{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "4a1aae78-88a6-4133-b905-7e46c8e3772f",
      "metadata": {
        "id": "4a1aae78-88a6-4133-b905-7e46c8e3772f"
      },
      "source": [
        "# Quick Start\n",
        "\n",
        "In this comprehensive quick start, we will build a support chatbot in LangGraph that can:\n",
        "\n",
        "- Answer common questions by searching the web\n",
        "- Maintain conversation state across calls\n",
        "- Route complex queries to a human for review\n",
        "- Use custom state to control its behavior\n",
        "- Rewind and explore alternative conversation paths\n",
        "\n",
        "We'll start with a basic chatbot and progressively add more sophisticated capabilities, introducing key LangGraph concepts along the way.\n",
        "\n",
        "## Setup\n",
        "\n",
        "First, install the required packages:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6f11d631-8679-4f28-822f-cdf1f2ddc21c",
      "metadata": {
        "id": "6f11d631-8679-4f28-822f-cdf1f2ddc21c"
      },
      "outputs": [],
      "source": [
        "%%capture --no-stderr\n",
        "%pip install -U langgraph langsmith langchain_google_genai"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a6d1e870-1bc0-4d44-86c0-96681ccf6113",
      "metadata": {
        "id": "a6d1e870-1bc0-4d44-86c0-96681ccf6113"
      },
      "source": [
        "Next, set your API keys:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "705d4020-6ee8-44cc-b1a5-8c34e7172fc7",
      "metadata": {
        "id": "705d4020-6ee8-44cc-b1a5-8c34e7172fc7"
      },
      "outputs": [],
      "source": [
        "import os\n",
        "from google.colab import userdata\n",
        "\n",
        "os.environ[\"LANGCHAIN_API_KEY\"] = userdata.get('LANGCHAIN_API_KEY')\n",
        "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
        "os.environ[\"LANGCHAIN_PROJECT\"] = \"quickstart\"\n",
        "\n",
        "gemini_api_key = userdata.get('GEMINI_API_KEY')"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain_google_genai import ChatGoogleGenerativeAI\n",
        "\n",
        "llm = ChatGoogleGenerativeAI(\n",
        "    model=\"gemini-1.5-flash\",\n",
        "    max_retries=2,\n",
        "    api_key=gemini_api_key\n",
        ")\n",
        "\n",
        "llm.invoke(\"greet me\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "BgEoJGobjGwM",
        "outputId": "47d27f0b-3e52-468b-afc2-609eba63f84f"
      },
      "id": "BgEoJGobjGwM",
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "AIMessage(content='Hello there! How can I help you today? \\n', additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-53e8fb0a-91a9-41cc-bc73-2dc6811ddad5-0', usage_metadata={'input_tokens': 3, 'output_tokens': 10, 'total_tokens': 13, 'input_token_details': {'cache_read': 0}})"
            ]
          },
          "metadata": {},
          "execution_count": 40
        }
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a98c72cf-33f9-4a37-9634-6c93a7c28815",
      "metadata": {
        "id": "a98c72cf-33f9-4a37-9634-6c93a7c28815"
      },
      "source": [
        "<div class=\"admonition tip\">\n",
        "    <p class=\"admonition-title\">Set up <a href=\"https://smith.langchain.com\">LangSmith</a> for LangGraph development</p>\n",
        "    <p style=\"padding-top: 5px;\">\n",
        "        Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>.\n",
        "    </p>\n",
        "</div>"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9c374e41-f9b7-439e-a520-6d8c853c5220",
      "metadata": {
        "id": "9c374e41-f9b7-439e-a520-6d8c853c5220"
      },
      "source": [
        "## Part 1: Build a Basic Chatbot\n",
        "\n",
        "We'll first create a simple chatbot using LangGraph. This chatbot will respond directly to user messages. Though simple, it will illustrate the core concepts of building with LangGraph. By the end of this section, you will have a built rudimentary chatbot.\n",
        "\n",
        "Start by creating a `StateGraph`. A `StateGraph` object defines the structure of our chatbot as a \"state machine\". We'll add `nodes` to represent the llm and functions our chatbot can call and `edges` to specify how the bot should transition between these functions."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "e58df974-7579-4f25-9d91-66389b94eba2",
      "metadata": {
        "id": "e58df974-7579-4f25-9d91-66389b94eba2"
      },
      "outputs": [],
      "source": [
        "from typing import Annotated\n",
        "\n",
        "from typing_extensions import TypedDict\n",
        "\n",
        "from langgraph.graph import StateGraph, START, END\n",
        "from langgraph.graph.message import add_messages\n",
        "\n",
        "\n",
        "class State(TypedDict):\n",
        "    # Messages have the type \"list\". The `add_messages` function\n",
        "    # in the annotation defines how this state key should be updated\n",
        "    # (in this case, it appends messages to the list, rather than overwriting them)\n",
        "    messages: Annotated[list, add_messages]\n",
        "\n",
        "\n",
        "graph_builder = StateGraph(State)"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# # # Example usage of add_messages\n",
        "# # new_messages_1 = [{\"role\": \"user\", \"content\": \"Hello\"}]\n",
        "# # new_messages_2 = [{\"role\": \"ai\", \"content\": \"Hi there!\"}]\n",
        "\n",
        "# # updated_state_1 = add_messages(new_messages_1, new_messages_2)\n",
        "\n",
        "\n",
        "# # print(\"Updated State 1:\", updated_state_1)\n",
        "\n",
        "# from typing import Annotated\n",
        "# from typing_extensions import TypedDict\n",
        "# from langgraph.graph import StateGraph\n",
        "\n",
        "# class State(TypedDict):\n",
        "#     messages: Annotated[list, add_messages]\n",
        "\n",
        "# builder = StateGraph(State)\n",
        "# builder.add_node(\"chatbot\", lambda state: {\"messages\": [(\"assistant\", \"Hello\")]})\n",
        "# builder.set_entry_point(\"chatbot\")\n",
        "# builder.set_finish_point(\"chatbot\")\n",
        "# graph = builder.compile()\n",
        "# graph.invoke({\"messages\": [{\"role\": \"user\", \"content\": \"Hello\"}]})"
      ],
      "metadata": {
        "id": "uNH1BDznrM1Q"
      },
      "id": "uNH1BDznrM1Q",
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "id": "31c755cd-8994-4867-bdff-96a55d7beae7",
      "metadata": {
        "id": "31c755cd-8994-4867-bdff-96a55d7beae7"
      },
      "source": [
        "<div class=\"admonition tip\">\n",
        "    <p class=\"admonition-title\">Note</p>\n",
        "    <p>\n",
        "    The first thing you do when you define a graph is define the <code>State</code> of the graph. The <code>State</code> consists of the schema of the graph as well as reducer functions which specify how to apply updates to the state. In our example <code>State</code> is a <code>TypedDict</code> with a single key: <code>messages</code>. The <code>messages</code> key is annotated with the <a href=\"https://langchain-ai.github.io/langgraph/reference/graphs/?h=add+messages#add_messages\"><code>add_messages</code></a> reducer function, which tells LangGraph to append new messages to the existing list, rather than overwriting it. State keys without an annotation will be overwritten by each update, storing the most recent value. Check out <a href=\"https://langchain-ai.github.io/langgraph/reference/graphs/?h=add+messages#add_messages\">this conceptual guide</a> to learn more about state, reducers and other low-level concepts.\n",
        "    </p>\n",
        "</div>"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4137feed-746e-4c72-a34a-f7a699ad5dcf",
      "metadata": {
        "id": "4137feed-746e-4c72-a34a-f7a699ad5dcf"
      },
      "source": [
        "So now our graph knows two things:\n",
        "\n",
        "1. Every `node` we define will receive the current `State` as input and return a value that updates that state.\n",
        "2. `messages` will be _appended_ to the current list, rather than directly overwritten. This is communicated via the prebuilt [`add_messages`](https://langchain-ai.github.io/langgraph/reference/graphs/?h=add+messages#add_messages) function in the `Annotated` syntax.\n",
        "\n",
        "Next, add a \"`chatbot`\" node. Nodes represent units of work. They are typically regular python functions."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "bc8c9137-8261-42ea-8e83-3590981d23e2",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "bc8c9137-8261-42ea-8e83-3590981d23e2",
        "outputId": "e3aea411-738b-4192-b082-8c10563b12e3"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<langgraph.graph.state.StateGraph at 0x7ac95ea7fc70>"
            ]
          },
          "metadata": {},
          "execution_count": 8
        }
      ],
      "source": [
        "def chatbot(state: State):\n",
        "    return {\"messages\": [llm.invoke(state[\"messages\"])]}\n",
        "\n",
        "\n",
        "# The first argument is the unique node name\n",
        "# The second argument is the function or object that will be called whenever\n",
        "# the node is used.\n",
        "graph_builder.add_node(\"chatbot\", chatbot)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b6c1dcd9-fb86-4649-81b4-ff6ce20a2e46",
      "metadata": {
        "id": "b6c1dcd9-fb86-4649-81b4-ff6ce20a2e46"
      },
      "source": [
        "**Notice** how the `chatbot` node function takes the current `State` as input and returns a dictionary containing an updated `messages` list under the key \"messages\". This is the basic pattern for all LangGraph node functions.\n",
        "\n",
        "The `add_messages` function in our `State` will append the llm's response messages to whatever messages are already in the state.\n",
        "\n",
        "Next, add an `entry` point. This tells our graph **where to start its work** each time we run it."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "e331e10d-ebcf-4144-9bd3-999b4d656dd3",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "e331e10d-ebcf-4144-9bd3-999b4d656dd3",
        "outputId": "c758dbd6-46f0-4bca-91e6-9b1603e17bff"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<langgraph.graph.state.StateGraph at 0x7ac95ea7fc70>"
            ]
          },
          "metadata": {},
          "execution_count": 9
        }
      ],
      "source": [
        "graph_builder.add_edge(START, \"chatbot\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0499c318-d1e6-46fa-a652-8f9e65313355",
      "metadata": {
        "id": "0499c318-d1e6-46fa-a652-8f9e65313355"
      },
      "source": [
        "Similarly, set a `finish` point. This instructs the graph **\"any time this node is run, you can exit.\"**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "075f0929-3591-4852-b2d3-eaadde40662d",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "075f0929-3591-4852-b2d3-eaadde40662d",
        "outputId": "e5239a95-c3fc-4215-8131-59ceaa4dc55a"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<langgraph.graph.state.StateGraph at 0x7ac95ea7fc70>"
            ]
          },
          "metadata": {},
          "execution_count": 10
        }
      ],
      "source": [
        "graph_builder.add_edge(\"chatbot\", END)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "65a9b88c-2c53-4d95-8eb1-d544a8946f65",
      "metadata": {
        "id": "65a9b88c-2c53-4d95-8eb1-d544a8946f65"
      },
      "source": [
        "Finally, we'll want to be able to run our graph. To do so, call \"`compile()`\" on the graph builder. This creates a \"`CompiledGraph`\" we can use invoke on our state."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0bb67a01-cf5c-4625-8c07-6e8c0af50fca",
      "metadata": {
        "id": "0bb67a01-cf5c-4625-8c07-6e8c0af50fca"
      },
      "outputs": [],
      "source": [
        "graph = graph_builder.compile()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0c39407b-d6f6-48a4-b1f6-31fc7f88b275",
      "metadata": {
        "id": "0c39407b-d6f6-48a4-b1f6-31fc7f88b275"
      },
      "source": [
        "You can visualize the graph using the `get_graph` method and one of the \"draw\" methods, like `draw_ascii` or `draw_png`. The `draw` methods each require additional dependencies."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "32e4f36e-72ce-4ade-bd7e-94880e0d456b",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 251
        },
        "id": "32e4f36e-72ce-4ade-bd7e-94880e0d456b",
        "outputId": "add7a59f-ebe8-4b92-c99d-814c2209b79b"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "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\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        }
      ],
      "source": [
        "from IPython.display import Image, display\n",
        "\n",
        "try:\n",
        "    display(Image(graph.get_graph().draw_mermaid_png()))\n",
        "except Exception:\n",
        "    # This requires some extra dependencies and is optional\n",
        "    pass"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "for event in graph.stream({\"messages\": [(\"user\", \"I am Junaid\")]}):\n",
        "    print(\"EVENT:\", list(event.values())[0][\"messages\"][-1].content)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "UFL71lmqrXN2",
        "outputId": "de868dae-b351-4914-ac3a-fb4cf76b3572"
      },
      "id": "UFL71lmqrXN2",
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "EVENT: Hello Junaid!  It's nice to meet you. How can I help you today?\n",
            "\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a98097a3-a126-4081-b21e-697ec1185fff",
      "metadata": {
        "id": "a98097a3-a126-4081-b21e-697ec1185fff"
      },
      "source": [
        "Now let's run the chatbot!\n",
        "\n",
        "**Tip:** You can exit the chat loop at any time by typing \"quit\", \"exit\", or \"q\"."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6a385b06-8d34-4a2d-aded-1cf4bb0ca590",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "6a385b06-8d34-4a2d-aded-1cf4bb0ca590",
        "outputId": "c30972e5-134c-4af1-a9bd-f0b3f123226f"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "User: I am Junaud\n",
            "Assistant: Okay, Junaud.  How can I help you today?\n",
            "\n",
            "User: What is my name\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "WARNING:langchain_google_genai.chat_models:Retrying langchain_google_genai.chat_models._chat_with_retry.<locals>._chat_with_retry in 2.0 seconds as it raised ResourceExhausted: 429 Resource has been exhausted (e.g. check quota)..\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "User: What do you know about LangGraph?\n",
            "Assistant: LangGraph (or LangChain-based Knowledge Graphs) refers to the practice of using the LangChain library to create, manage, and query knowledge graphs.  It's not a specific product or standalone tool, but rather a methodology leveraging LangChain's capabilities.\n",
            "\n",
            "Here's a breakdown of how LangChain facilitates knowledge graph interactions:\n",
            "\n",
            "* **Creating Knowledge Graphs:** LangChain doesn't natively *store* a knowledge graph in a traditional graph database like Neo4j. Instead, it helps you build and represent knowledge graph information using various methods:\n",
            "    * **In-memory graphs:**  Simple graphs can be constructed directly within Python using libraries like `networkx`. LangChain can interact with these.\n",
            "    * **Vector databases:**  Embeddings of text chunks representing entities and relationships can be stored in vector databases like Pinecone, Weaviate, or Chroma.  LangChain provides tools to query these databases semantically, effectively acting as a knowledge graph interface.\n",
            "    * **External knowledge graphs:** LangChain can connect to and query existing knowledge graphs through integrations with graph databases or APIs.\n",
            "\n",
            "* **Populating Knowledge Graphs:** LangChain can help populate these representations using several techniques:\n",
            "    * **LLMs for Extraction:** Large language models can be used to extract entities, relationships, and facts from text documents.\n",
            "    * **Structured Data Integration:**  Existing structured data sources can be transformed and loaded into the chosen knowledge graph representation.\n",
            "    * **External APIs:** Data from external APIs can be incorporated to enrich the knowledge graph.\n",
            "\n",
            "* **Querying Knowledge Graphs:**  LangChain provides tools for querying the knowledge graph representation, whether it's an in-memory graph, a vector database, or an external knowledge graph.  This might involve:\n",
            "    * **Natural language queries:**  Using LLMs to translate natural language questions into structured queries.\n",
            "    * **Traversal-based queries:**  Navigating the graph structure to find related entities and information.\n",
            "    * **Semantic similarity searches:** Finding relevant information based on the meaning of concepts, often using embeddings and vector databases.\n",
            "\n",
            "* **Reasoning over Knowledge Graphs:** LangChain can be used to build more complex reasoning capabilities on top of the knowledge graph. This might include:\n",
            "    * **Path finding:**  Finding connections between entities.\n",
            "    * **Inference:**  Deducing new facts based on existing knowledge.\n",
            "    * **Question answering:**  Providing answers based on information in the graph.\n",
            "\n",
            "\n",
            "**Key Advantages of using LangChain for Knowledge Graphs:**\n",
            "\n",
            "* **Flexibility:**  LangChain offers a flexible approach to working with knowledge graphs, supporting different representations and data sources.\n",
            "* **Ease of use:**  LangChain simplifies the process of building and querying knowledge graphs, especially for those already familiar with the library.\n",
            "* **Integration with LLMs:**  The tight integration with LLMs allows for powerful natural language processing and reasoning capabilities.\n",
            "\n",
            "**Limitations:**\n",
            "\n",
            "* **Scalability:**  For very large knowledge graphs, performance might become an issue, especially when relying on in-memory representations or complex LLM-based reasoning.  Dedicated graph databases are typically better suited for large-scale applications.\n",
            "* **Complexity:** While LangChain simplifies many aspects, building and managing a robust knowledge graph system can still be complex.\n",
            "\n",
            "\n",
            "In summary, \"LangGraph\" isn't a specific technology but a powerful methodology using LangChain to interact with and leverage knowledge graph-like structures.  It provides a flexible and accessible way to incorporate knowledge into applications, particularly when combined with the power of large language models.\n",
            "\n"
          ]
        }
      ],
      "source": [
        "def stream_graph_updates(user_input: str):\n",
        "    for event in graph.stream({\"messages\": [(\"user\", user_input)]}):\n",
        "        for value in event.values():\n",
        "            print(\"Assistant:\", value[\"messages\"][-1].content)\n",
        "\n",
        "\n",
        "while True:\n",
        "    try:\n",
        "        user_input = input(\"User: \")\n",
        "        if user_input.lower() in [\"quit\", \"exit\", \"q\"]:\n",
        "            print(\"Goodbye!\")\n",
        "            break\n",
        "\n",
        "        stream_graph_updates(user_input)\n",
        "    except:\n",
        "        # fallback if input() is not available\n",
        "        user_input = \"What do you know about LangGraph?\"\n",
        "        print(\"User: \" + user_input)\n",
        "        stream_graph_updates(user_input)\n",
        "        break"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "98e1cdb5-869a-41ea-9dab-e28cfc524499",
      "metadata": {
        "id": "98e1cdb5-869a-41ea-9dab-e28cfc524499"
      },
      "source": [
        "**Congratulations!** You've built your first chatbot using LangGraph. This bot can engage in basic conversation by taking user input and generating responses using an LLM. You can inspect a [LangSmith Trace](https://smith.langchain.com/public/7527e308-9502-4894-b347-f34385740d5a/r) for the call above at the provided link.\n",
        "\n",
        "However, you may have noticed that the bot's knowledge is limited to what's in its training data. In the next part, we'll add a web search tool to expand the bot's knowledge and make it more capable.\n",
        "\n",
        "Below is the full code for this section for your reference:\n",
        "\n",
        "<details>\n",
        "<summary>Full Code</summary>\n",
        "    <pre>\n",
        "        \n",
        "```python\n",
        "from typing import Annotated\n",
        "\n",
        "from langchain_anthropic import ChatAnthropic\n",
        "from typing_extensions import TypedDict\n",
        "\n",
        "from langgraph.graph import StateGraph\n",
        "from langgraph.graph.message import add_messages\n",
        "\n",
        "\n",
        "class State(TypedDict):\n",
        "    messages: Annotated[list, add_messages]\n",
        "\n",
        "\n",
        "graph_builder = StateGraph(State)\n",
        "\n",
        "\n",
        "llm = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n",
        "\n",
        "\n",
        "def chatbot(state: State):\n",
        "    return {\"messages\": [llm.invoke(state[\"messages\"])]}\n",
        "\n",
        "\n",
        "# The first argument is the unique node name\n",
        "# The second argument is the function or object that will be called whenever\n",
        "# the node is used.\n",
        "graph_builder.add_node(\"chatbot\", chatbot)\n",
        "graph_builder.set_entry_point(\"chatbot\")\n",
        "graph_builder.set_finish_point(\"chatbot\")\n",
        "graph = graph_builder.compile()\n",
        "```\n",
        "\n",
        "</pre>\n",
        "</details>"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f22c5d4a-3134-413c-81fe-dd9752fbeb66",
      "metadata": {
        "id": "f22c5d4a-3134-413c-81fe-dd9752fbeb66"
      },
      "source": [
        "## Part 2: Enhancing the Chatbot with Tools\n",
        "\n",
        "To handle queries our chatbot can't answer \"from memory\", we'll integrate a web search tool. Our bot can use this tool to find relevant information and provide better responses.\n",
        "\n",
        "#### Requirements\n",
        "\n",
        "Before we start, make sure you have the necessary packages installed and API keys set up:\n",
        "\n",
        "First, install the requirements to use the [Tavily Search Engine](https://python.langchain.com/docs/integrations/tools/tavily_search/), and set your [TAVILY_API_KEY](https://tavily.com/)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "7451151f-41fc-4af0-9359-024ae51b7225",
      "metadata": {
        "id": "7451151f-41fc-4af0-9359-024ae51b7225"
      },
      "outputs": [],
      "source": [
        "%%capture --no-stderr\n",
        "%pip install -U tavily-python langchain_community"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0c52923c-5665-4f8c-a1ba-9799e369c49e",
      "metadata": {
        "id": "0c52923c-5665-4f8c-a1ba-9799e369c49e"
      },
      "outputs": [],
      "source": [
        "os.environ[\"TAVILY_API_KEY\"] = userdata.get(\"TAVILY_API_KEY\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "591ce9ba-c431-4165-b815-25c944ef7cdb",
      "metadata": {
        "id": "591ce9ba-c431-4165-b815-25c944ef7cdb"
      },
      "source": [
        "Next, define the tool:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "35c8978e-c07d-4dd0-a97b-0ce3a723eea5",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "35c8978e-c07d-4dd0-a97b-0ce3a723eea5",
        "outputId": "8d85264b-fd2d-40c6-fd99-ba3545e1097a"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[{'url': 'https://medium.com/@cplog/introduction-to-langgraph-a-beginners-guide-14f9be027141',\n",
              "  'content': 'Nodes: Nodes are the building blocks of your LangGraph. Each node represents a function or a computation step. You define nodes to perform specific tasks, such as processing input, making'},\n",
              " {'url': 'https://www.datacamp.com/tutorial/langgraph-tutorial',\n",
              "  'content': \"In LangGraph, each node represents an LLM agent, and the edges are the communication channels between these agents. This structure allows for clear and manageable workflows, where each agent performs specific tasks and passes information to other agents as needed. State management. One of LangGraph's standout features is its automatic state\"}]"
            ]
          },
          "metadata": {},
          "execution_count": 19
        }
      ],
      "source": [
        "from langchain_community.tools.tavily_search import TavilySearchResults\n",
        "\n",
        "tool = TavilySearchResults(max_results=2)\n",
        "tools = [tool]\n",
        "tool.invoke(\"What's a 'node' in LangGraph?\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7f503f02-d23d-42e8-9b5d-eb2681b242f4",
      "metadata": {
        "id": "7f503f02-d23d-42e8-9b5d-eb2681b242f4"
      },
      "source": [
        "The results are page summaries our chat bot can use to answer questions.\n",
        "\n",
        "\n",
        "Next, we'll start defining our graph. The following is all **the same as in Part 1**, except we have added `bind_tools` on our LLM. This lets the LLM know the correct JSON format to use if it wants to use our search engine."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "dc5af88b-47d2-43bf-9a2c-6c07506b1732",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "dc5af88b-47d2-43bf-9a2c-6c07506b1732",
        "outputId": "9f2c7cf7-e3da-4756-f52a-3db8c09e7ad8"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<langgraph.graph.state.StateGraph at 0x7ac9511ff4f0>"
            ]
          },
          "metadata": {},
          "execution_count": 41
        }
      ],
      "source": [
        "from typing import Annotated\n",
        "\n",
        "from typing_extensions import TypedDict\n",
        "\n",
        "from langgraph.graph import StateGraph, START, END\n",
        "from langgraph.graph.message import add_messages\n",
        "\n",
        "\n",
        "class State(TypedDict):\n",
        "    messages: Annotated[list, add_messages]\n",
        "\n",
        "\n",
        "graph_builder = StateGraph(State)\n",
        "\n",
        "\n",
        "# Modification: tell the LLM which tools it can call\n",
        "llm_with_tools = llm.bind_tools(tools)\n",
        "\n",
        "\n",
        "def chatbot(state: State):\n",
        "    return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
        "\n",
        "\n",
        "graph_builder.add_node(\"chatbot\", chatbot)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d1e84cfc-b1b2-48e3-8550-152a408c3926",
      "metadata": {
        "id": "d1e84cfc-b1b2-48e3-8550-152a408c3926"
      },
      "source": [
        "Next we need to create a function to actually run the tools if they are called. We'll do this by adding the tools to a new node.\n",
        "\n",
        "Below, we implement a `BasicToolNode` that checks the most recent message in the state and calls tools if the message contains `tool_calls`. It relies on the LLM's `tool_calling` support, which is available in Anthropic, OpenAI, Google Gemini, and a number of other LLM providers.\n",
        "\n",
        "We will later replace this with LangGraph's prebuilt [ToolNode](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode) to speed things up, but building it ourselves first is instructive."
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "tools_by_name = {tool.name: tool for tool in tools}\n",
        "print(type(tools_by_name))\n",
        "print(tools_by_name)\n",
        "\n",
        "tools_by_name[\"tavily_search_results_json\"]\n",
        "\n",
        "llm_with_tools.invoke(\"search using tool and what polls say about winner 2024 presidential elections\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "lp4gYto3rlFt",
        "outputId": "1093dd41-44f6-45e2-bcdd-b0d8dcd714f5"
      },
      "id": "lp4gYto3rlFt",
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "<class 'dict'>\n",
            "{'tavily_search_results_json': TavilySearchResults(max_results=2, api_wrapper=TavilySearchAPIWrapper(tavily_api_key=SecretStr('**********')))}\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\"query\": \"2024 presidential election polls\"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-2bbf8231-87c7-4a15-87c2-e3091cd02669-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': '2024 presidential election polls'}, 'id': 'f7662c20-0bc5-4788-b7d0-cf4794149066', 'type': 'tool_call'}], usage_metadata={'input_tokens': 90, 'output_tokens': 16, 'total_tokens': 106, 'input_token_details': {'cache_read': 0}})"
            ]
          },
          "metadata": {},
          "execution_count": 27
        }
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "12f1fc14-cd91-4cd4-9f2e-1d007f8beafc",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "12f1fc14-cd91-4cd4-9f2e-1d007f8beafc",
        "outputId": "b9bf741c-a0d0-4546-8063-b39f19a94833"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<langgraph.graph.state.StateGraph at 0x7ac9511ff4f0>"
            ]
          },
          "metadata": {},
          "execution_count": 42
        }
      ],
      "source": [
        "import json\n",
        "\n",
        "from langchain_core.messages import ToolMessage\n",
        "\n",
        "\n",
        "class BasicToolNode:\n",
        "    \"\"\"A node that runs the tools requested in the last AIMessage.\"\"\"\n",
        "\n",
        "    def __init__(self, tools: list) -> None:\n",
        "        self.tools_by_name = {tool.name: tool for tool in tools}\n",
        "\n",
        "    def __call__(self, inputs: dict):\n",
        "        if messages := inputs.get(\"messages\", []):\n",
        "            message = messages[-1]\n",
        "        else:\n",
        "            raise ValueError(\"No message found in input\")\n",
        "        outputs = []\n",
        "        for tool_call in message.tool_calls:\n",
        "            tool_result = self.tools_by_name[tool_call[\"name\"]].invoke(\n",
        "                tool_call[\"args\"]\n",
        "            )\n",
        "            outputs.append(\n",
        "                ToolMessage(\n",
        "                    content=json.dumps(tool_result),\n",
        "                    name=tool_call[\"name\"],\n",
        "                    tool_call_id=tool_call[\"id\"],\n",
        "                )\n",
        "            )\n",
        "        return {\"messages\": outputs}\n",
        "\n",
        "\n",
        "tool_node = BasicToolNode(tools=[tool])\n",
        "graph_builder.add_node(\"tools\", tool_node)"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain_core.messages import HumanMessage, AIMessage\n",
        "\n",
        "messages = [\n",
        "AIMessage(content='',\n",
        "          additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\"query\": \"2024 presidential elections polls\"}'}},\n",
        "          response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []},\n",
        "          id='run-621e608b-24ae-4d50-8605-33415037f7df-0',\n",
        "          tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': '2024 presidential elections polls'}, 'id': '826c7148-a96a-4f79-9c64-325c97489d5e', 'type': 'tool_call'}],\n",
        "          usage_metadata={'input_tokens': 90, 'output_tokens': 16, 'total_tokens': 106, 'input_token_details': {'cache_read': 0}})\n",
        "]\n",
        "\n",
        "tool_node(inputs={\"messages\": messages})"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "zZrtm_chsEVV",
        "outputId": "0998ed98-0d40-45ad-aaf9-2496164cc5d5"
      },
      "id": "zZrtm_chsEVV",
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'messages': [ToolMessage(content='[{\"url\": \"https://projects.fivethirtyeight.com/2024-election-forecast/\", \"content\": \"What the polls say about the 2024 election. ... The 538 team discusses whether early voting data is a good indicator of who will win the 2024 presidential election. September 24, 2024. How Harris talks about race and gender on the campaign trail | 538 Politics podcast.\"}, {\"url\": \"https://www.270towin.com/2024-presidential-election-polls/\", \"content\": \"This page displays the current 270toWin Polling Average for each state. Where that is not applicable, the most recent 2024 presidential election poll is used. The average methodology is summarized below the table. The default view is Harris vs. Trump head-to-head. To view averages including 3rd party candidates, select the checkbox near the\"}]', name='tavily_search_results_json', tool_call_id='826c7148-a96a-4f79-9c64-325c97489d5e')]}"
            ]
          },
          "metadata": {},
          "execution_count": 31
        }
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b049afc4-7757-40ba-8e00-589d378e816d",
      "metadata": {
        "id": "b049afc4-7757-40ba-8e00-589d378e816d"
      },
      "source": [
        "With the tool node added, we can define the `conditional_edges`.\n",
        "\n",
        "Recall that **edges** route the control flow from one node to the next. **Conditional edges** usually contain \"if\" statements to route to different nodes depending on the current graph state. These functions receive the current graph `state` and return a string or list of strings indicating which node(s) to call next.\n",
        "\n",
        "Below, call define a router function called `route_tools`, that checks for tool_calls in the chatbot's output. Provide this function to the graph by calling `add_conditional_edges`, which tells the graph that whenever the `chatbot` node completes to check this function to see where to go next.\n",
        "\n",
        "The condition will route to `tools` if tool calls are present and `END` if not.\n",
        "\n",
        "Later, we will replace this with the prebuilt [tools_condition](https://langchain-ai.github.io/langgraph/reference/prebuilt/#tools_condition) to be more concise, but implementing it ourselves first makes things more clear."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d662df94-66ac-4c6c-92f0-4c93620f1c74",
      "metadata": {
        "id": "d662df94-66ac-4c6c-92f0-4c93620f1c74"
      },
      "outputs": [],
      "source": [
        "from typing import Literal\n",
        "\n",
        "\n",
        "def route_tools(\n",
        "    state: State,\n",
        "):\n",
        "    \"\"\"\n",
        "    Use in the conditional_edge to route to the ToolNode if the last message\n",
        "    has tool calls. Otherwise, route to the end.\n",
        "    \"\"\"\n",
        "    if isinstance(state, list):\n",
        "        ai_message = state[-1]\n",
        "    elif messages := state.get(\"messages\", []):\n",
        "        ai_message = messages[-1]\n",
        "    else:\n",
        "        raise ValueError(f\"No messages found in input state to tool_edge: {state}\")\n",
        "    if hasattr(ai_message, \"tool_calls\") and len(ai_message.tool_calls) > 0:\n",
        "        return \"tools\"\n",
        "    return END\n",
        "\n",
        "\n",
        "# The `tools_condition` function returns \"tools\" if the chatbot asks to use a tool, and \"END\" if\n",
        "# it is fine directly responding. This conditional routing defines the main agent loop.\n",
        "graph_builder.add_conditional_edges(\n",
        "    \"chatbot\",\n",
        "    route_tools,\n",
        "    # The following dictionary lets you tell the graph to interpret the condition's outputs as a specific node\n",
        "    # It defaults to the identity function, but if you\n",
        "    # want to use a node named something else apart from \"tools\",\n",
        "    # You can update the value of the dictionary to something else\n",
        "    # e.g., \"tools\": \"my_tools\"\n",
        "    {\"tools\": \"tools\", END: END},\n",
        ")\n",
        "# Any time a tool is called, we return to the chatbot to decide the next step\n",
        "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
        "graph_builder.add_edge(START, \"chatbot\")\n",
        "graph = graph_builder.compile()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a2aa67c2-dd1b-4bf2-8c64-eea44296d15f",
      "metadata": {
        "id": "a2aa67c2-dd1b-4bf2-8c64-eea44296d15f"
      },
      "source": [
        "**Notice** that conditional edges start from a single node. This tells the graph \"any time the '`chatbot`' node runs, either go to 'tools' if it calls a tool, or end the loop if it responds directly.\n",
        "\n",
        "Like the prebuilt `tools_condition`, our function returns the `END` string if no tool calls are made. When the graph transitions to `END`, it has no more tasks to complete and ceases execution. Because the condition can return `END`, we don't need to explicitly set a `finish_point` this time. Our graph already has a way to finish!\n",
        "\n",
        "Let's visualize the graph we've built. The following function has some additional dependencies to run that are unimportant for this tutorial."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "8b49509c-9d97-457c-a76a-c495fb30ccbc",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 266
        },
        "id": "8b49509c-9d97-457c-a76a-c495fb30ccbc",
        "outputId": "188cf829-039f-4195-968d-edb4e356c4fa"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "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\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        }
      ],
      "source": [
        "from IPython.display import Image, display\n",
        "\n",
        "try:\n",
        "    display(Image(graph.get_graph().draw_mermaid_png()))\n",
        "except Exception:\n",
        "    # This requires some extra dependencies and is optional\n",
        "    pass"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c59593ef-5073-4279-931e-828dae971f23",
      "metadata": {
        "id": "c59593ef-5073-4279-931e-828dae971f23"
      },
      "source": [
        "Now we can ask the bot questions outside its training data."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "051dc374-67cc-4371-9dd1-221e07593148",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "051dc374-67cc-4371-9dd1-221e07593148",
        "outputId": "11eb4d6b-1a15-41cb-94d8-24fa9f61e627"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "User: When will be 2024 presidential electionsresults be announed US\n",
            "Assistant: \n",
            "Assistant: [{\"url\": \"https://www.usatoday.com/story/news/politics/elections/2024/10/10/how-long-will-2024-election-results-take/75604170007/\", \"content\": \"ELECTIONS Elections Each state handles its elections differently, ranging from weeks-long early voting to strict voter ID laws. More:A flurry of lawsuits on state voting rules could influence 2024 election results Sign-up for Your Vote: Text with the USA TODAY elections team. In many states, early voting ballot counting could not start until Election Day, and others could start as early as 18 days, but results could not be disclosed until polls were closed. It took 35 days, the longest amount of time in modern history, to declare who won the 2000 presidential election, coming down to just 537 votes. It still remains a contentious election to this day \\u2014 determined by a difference of 537\\u00a0votes.\"}, {\"url\": \"https://www.usatoday.com/story/news/politics/elections/2024/10/30/election-2024-live-updates-trump-harris/75923764007/\", \"content\": \"Election 2024 live updates: Trump, Harris in swing states; new polls Election 2024 live updates: Harris and Trump head to pivotal swing states; latest polls Marina Pitofsky Alison Dirr Terry Moseley Terry Collins Rebecca Morin Francesca Chambers Less than a week before Election Day, Vice President\\u00a0Kamala Harris\\u00a0and former President\\u00a0Donald Trump\\u00a0are deadlocked in the battleground state of Michigan, according to an exclusive new USA TODAY/Suffolk University poll. The 2024 presidential race is set to be neck-and-neck up to Election Day. In Real Clear Politics' average of national polls, Trump leads Harris by 0.4 percentage points, well within the margin of error for all of the surveys included.\"}]\n",
            "Assistant: The results of the 2024 US presidential election are expected to be announced on Election Day, which is November 5, 2024. However, the actual declaration of the winner could take longer depending on the closeness of the race and the number of ballots that need to be counted. In the 2000 election, it took 35 days to declare a winner. \n",
            "\n",
            "User: q\n",
            "Goodbye!\n"
          ]
        }
      ],
      "source": [
        "while True:\n",
        "    try:\n",
        "        user_input = input(\"User: \")\n",
        "        if user_input.lower() in [\"quit\", \"exit\", \"q\"]:\n",
        "            print(\"Goodbye!\")\n",
        "            break\n",
        "\n",
        "        stream_graph_updates(user_input)\n",
        "    except:\n",
        "        # fallback if input() is not available\n",
        "        user_input = \"What do you know about LangGraph?\"\n",
        "        print(\"User: \" + user_input)\n",
        "        stream_graph_updates(user_input)\n",
        "        break"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "89da9e85-2e5d-49c2-8cbd-572cbdb89135",
      "metadata": {
        "id": "89da9e85-2e5d-49c2-8cbd-572cbdb89135"
      },
      "source": [
        "**Congrats!** You've created a conversational agent in langgraph that can use a search engine to retrieve updated information when needed. Now it can handle a wider range of user queries. To inspect all the steps your agent just took, check out this [LangSmith trace](https://smith.langchain.com/public/4fbd7636-25af-4638-9587-5a02fdbb0172/r).\n",
        "\n",
        "Our chatbot still can't remember past interactions on its own, limiting its ability to have coherent, multi-turn conversations. In the next part, we'll add **memory** to address this.\n",
        "\n",
        "\n",
        "The full code for the graph we've created in this section is reproduced below, replacing our `BasicToolNode` for the prebuilt [ToolNode](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode), and our `route_tools` condition with the prebuilt [tools_condition](https://langchain-ai.github.io/langgraph/reference/prebuilt/#tools_condition)\n",
        "\n",
        "<details>\n",
        "<summary>Full Code</summary>\n",
        "    <pre>\n",
        "\n",
        "```python\n",
        "from typing import Annotated\n",
        "\n",
        "from langchain_anthropic import ChatAnthropic\n",
        "from langchain_community.tools.tavily_search import TavilySearchResults\n",
        "from langchain_core.messages import BaseMessage\n",
        "from typing_extensions import TypedDict\n",
        "\n",
        "from langgraph.graph import StateGraph\n",
        "from langgraph.graph.message import add_messages\n",
        "from langgraph.prebuilt import ToolNode, tools_condition\n",
        "\n",
        "\n",
        "class State(TypedDict):\n",
        "    messages: Annotated[list, add_messages]\n",
        "\n",
        "\n",
        "graph_builder = StateGraph(State)\n",
        "\n",
        "\n",
        "tool = TavilySearchResults(max_results=2)\n",
        "tools = [tool]\n",
        "llm = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n",
        "llm_with_tools = llm.bind_tools(tools)\n",
        "\n",
        "\n",
        "def chatbot(state: State):\n",
        "    return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
        "\n",
        "\n",
        "graph_builder.add_node(\"chatbot\", chatbot)\n",
        "\n",
        "tool_node = ToolNode(tools=[tool])\n",
        "graph_builder.add_node(\"tools\", tool_node)\n",
        "\n",
        "graph_builder.add_conditional_edges(\n",
        "    \"chatbot\",\n",
        "    tools_condition,\n",
        ")\n",
        "# Any time a tool is called, we return to the chatbot to decide the next step\n",
        "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
        "graph_builder.set_entry_point(\"chatbot\")\n",
        "graph = graph_builder.compile()\n",
        "```\n",
        "\n",
        "</pre>\n",
        "</details>"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ae45f2aa-396f-4f3f-848b-7750611617f8",
      "metadata": {
        "id": "ae45f2aa-396f-4f3f-848b-7750611617f8"
      },
      "source": [
        "## Part 3: Adding Memory to the Chatbot\n",
        "\n",
        "Our chatbot can now use tools to answer user questions, but it doesn't remember the context of previous interactions. This limits its ability to have coherent, multi-turn conversations.\n",
        "\n",
        "LangGraph solves this problem through **persistent checkpointing**. If you provide a `checkpointer` when compiling the graph and a `thread_id` when calling your graph, LangGraph automatically saves the state after each step. When you invoke the graph again using the same `thread_id`, the graph loads its saved state, allowing the chatbot to pick up where it left off.\n",
        "\n",
        "We will see later that **checkpointing** is _much_ more powerful than simple chat memory - it lets you save and resume complex state at any time for error recovery, human-in-the-loop workflows, time travel interactions, and more. But before we get too ahead of ourselves, let's add checkpointing to enable multi-turn conversations.\n",
        "\n",
        "To get started, create a `MemorySaver` checkpointer."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6baafdf6-6803-4305-9381-9dc970468a4d",
      "metadata": {
        "id": "6baafdf6-6803-4305-9381-9dc970468a4d"
      },
      "outputs": [],
      "source": [
        "from langgraph.checkpoint.memory import MemorySaver\n",
        "\n",
        "memory = MemorySaver()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "08d3d11a-1b42-4cbb-8e11-2a4294263d90",
      "metadata": {
        "id": "08d3d11a-1b42-4cbb-8e11-2a4294263d90"
      },
      "source": [
        "**Notice** we're using an in-memory checkpointer. This is convenient for our tutorial (it saves it all in-memory). In a production application, you would likely change this to use `SqliteSaver` or `PostgresSaver` and connect to your own DB.\n",
        "\n",
        "Next define the graph. Now that you've already built your own `BasicToolNode`, we'll replace it with LangGraph's prebuilt `ToolNode` and `tools_condition`, since these do some nice things like parallel API execution. Apart from that, the following is all copied from Part 2."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "e6a51f1e-00de-4701-8931-de8cf19294ae",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "e6a51f1e-00de-4701-8931-de8cf19294ae",
        "outputId": "202de1e8-4fa8-4889-fc57-bb1469bd2d09"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<langgraph.graph.state.StateGraph at 0x7ac9515e3dc0>"
            ]
          },
          "metadata": {},
          "execution_count": 49
        }
      ],
      "source": [
        "from typing import Annotated\n",
        "\n",
        "from langchain_community.tools.tavily_search import TavilySearchResults\n",
        "from langchain_core.messages import BaseMessage\n",
        "from typing_extensions import TypedDict\n",
        "\n",
        "from langgraph.graph import StateGraph, START, END\n",
        "from langgraph.graph.message import add_messages\n",
        "from langgraph.prebuilt import ToolNode, tools_condition\n",
        "\n",
        "\n",
        "class State(TypedDict):\n",
        "    messages: Annotated[list, add_messages]\n",
        "\n",
        "\n",
        "graph_builder = StateGraph(State)\n",
        "\n",
        "\n",
        "tool = TavilySearchResults(max_results=2)\n",
        "tools = [tool]\n",
        "llm_with_tools = llm.bind_tools(tools)\n",
        "\n",
        "\n",
        "def chatbot(state: State):\n",
        "    return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
        "\n",
        "\n",
        "graph_builder.add_node(\"chatbot\", chatbot)\n",
        "\n",
        "tool_node = ToolNode(tools=[tool])\n",
        "graph_builder.add_node(\"tools\", tool_node)\n",
        "\n",
        "graph_builder.add_conditional_edges(\n",
        "    \"chatbot\",\n",
        "    tools_condition,\n",
        ")\n",
        "# Any time a tool is called, we return to the chatbot to decide the next step\n",
        "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
        "graph_builder.add_edge(START, \"chatbot\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8a292dfe-764f-4561-90aa-71317d679d3e",
      "metadata": {
        "id": "8a292dfe-764f-4561-90aa-71317d679d3e"
      },
      "source": [
        "Finally, compile the graph with the provided checkpointer."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "a06548bf-81fa-4436-b4c1-f68601fb4187",
      "metadata": {
        "id": "a06548bf-81fa-4436-b4c1-f68601fb4187"
      },
      "outputs": [],
      "source": [
        "graph = graph_builder.compile(checkpointer=memory)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "df01805c-4458-4474-b13b-59ecfe228f12",
      "metadata": {
        "id": "df01805c-4458-4474-b13b-59ecfe228f12"
      },
      "source": [
        "Notice the connectivity of the graph hasn't changed since Part 2. All we are doing is checkpointing the `State` as the graph works through each node."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "761d15fb-d5e2-4d50-a630-126d77e77294",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 266
        },
        "id": "761d15fb-d5e2-4d50-a630-126d77e77294",
        "outputId": "1528bd24-019b-4834-9d61-97b7e5390b97"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "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\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        }
      ],
      "source": [
        "from IPython.display import Image, display\n",
        "\n",
        "try:\n",
        "    display(Image(graph.get_graph().draw_mermaid_png()))\n",
        "except Exception:\n",
        "    # This requires some extra dependencies and is optional\n",
        "    pass"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2c8265ef-e5b4-4c32-9856-5572b5652142",
      "metadata": {
        "id": "2c8265ef-e5b4-4c32-9856-5572b5652142"
      },
      "source": [
        "Now you can interact with your bot! First, pick a thread to use as the key for this conversation."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "be7b5abb-04ef-4d53-83d1-d4d3139cc43a",
      "metadata": {
        "id": "be7b5abb-04ef-4d53-83d1-d4d3139cc43a"
      },
      "outputs": [],
      "source": [
        "config = {\"configurable\": {\"thread_id\": \"1\"}}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d0b1a5ee-7fa2-475c-a9db-749694b90ba9",
      "metadata": {
        "id": "d0b1a5ee-7fa2-475c-a9db-749694b90ba9"
      },
      "source": [
        "Next, call your chat bot."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "dba1b168-f8e0-496d-9bd6-37198fb4776e",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "dba1b168-f8e0-496d-9bd6-37198fb4776e",
        "outputId": "78ba9349-16df-46c2-a125-80ca110392ac"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "Hi there! My name is Junaid.\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "Nice to meet you, Junaid! How can I help you today?\n"
          ]
        }
      ],
      "source": [
        "user_input = \"Hi there! My name is Junaid.\"\n",
        "\n",
        "# The config is the **second positional argument** to stream() or invoke()!\n",
        "events = graph.stream(\n",
        "    {\"messages\": [(\"user\", user_input)]}, config, stream_mode=\"values\"\n",
        ")\n",
        "for event in events:\n",
        "    event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "33c6b470-5082-4c3e-b732-34de47c88735",
      "metadata": {
        "id": "33c6b470-5082-4c3e-b732-34de47c88735"
      },
      "source": [
        "**Note:** The config was provided as the **second positional argument** when calling our graph. It importantly is _not_ nested within the graph inputs (`{'messages': []}`).\n",
        "\n",
        "Let's ask a followup: see if it remembers your name."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "f5447778-53d7-47f3-801b-f47bcf2185a0",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "f5447778-53d7-47f3-801b-f47bcf2185a0",
        "outputId": "f10975f6-6fbe-448f-d2bc-6847c6d0d9df"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "Remember my name?\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "I will try my best to remember your name, Junaid! 😊  Is there anything else I can help you with?\n"
          ]
        }
      ],
      "source": [
        "user_input = \"Remember my name?\"\n",
        "\n",
        "# The config is the **second positional argument** to stream() or invoke()!\n",
        "events = graph.stream(\n",
        "    {\"messages\": [(\"user\", user_input)]}, config, stream_mode=\"values\"\n",
        ")\n",
        "for event in events:\n",
        "    event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "33be4cd8-f96f-4949-9d1f-48054502e5d0",
      "metadata": {
        "id": "33be4cd8-f96f-4949-9d1f-48054502e5d0"
      },
      "source": [
        "**Notice** that we aren't using an external list for memory: it's all handled by the checkpointer! You can inspect the full execution in this [LangSmith trace](https://smith.langchain.com/public/29ba22b5-6d40-4fbe-8d27-b369e3329c84/r) to see what's going on.\n",
        "\n",
        "Don't believe me? Try this using a different config."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "4527cf9a-b191-4bde-858a-e33a74a48c55",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "4527cf9a-b191-4bde-858a-e33a74a48c55",
        "outputId": "526bf0e0-28e8-461b-86c5-8c1a1e06584b"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "Remember my name?\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "I am sorry, I do not have memory of past conversations. If you would like to tell me your name, I will be happy to remember it for this conversation.\n"
          ]
        }
      ],
      "source": [
        "# The only difference is we change the `thread_id` here to \"2\" instead of \"1\"\n",
        "events = graph.stream(\n",
        "    {\"messages\": [(\"user\", user_input)]},\n",
        "    {\"configurable\": {\"thread_id\": \"2\"}},\n",
        "    stream_mode=\"values\",\n",
        ")\n",
        "for event in events:\n",
        "    event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5eeccbf0-ed74-4838-a7e9-31910d82b0b2",
      "metadata": {
        "id": "5eeccbf0-ed74-4838-a7e9-31910d82b0b2"
      },
      "source": [
        "**Notice** that the **only** change we've made is to modify the `thread_id` in the config. See this call's [LangSmith trace](https://smith.langchain.com/public/51a62351-2f0a-4058-91cc-9996c5561428/r) for comparison.\n",
        "\n",
        "By now, we have made a few checkpoints across two different threads. But what goes into a checkpoint? To inspect a graph's `state` for a given config at any time, call `get_state(config)`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0be77c25-1423-4f2d-9b2d-28530cc761a4",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "0be77c25-1423-4f2d-9b2d-28530cc761a4",
        "outputId": "36f58adc-443c-447e-cedc-fbf7eb2967e2"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "StateSnapshot(values={'messages': [HumanMessage(content='Hi there! My name is Junaid.', additional_kwargs={}, response_metadata={}, id='e4da1c8c-38a4-4278-96e1-a3d23f0cd105'), AIMessage(content='Nice to meet you, Junaid! How can I help you today? \\n', additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-0859a4e4-9c4a-426a-8558-b6af181c40d8-0', usage_metadata={'input_tokens': 84, 'output_tokens': 16, 'total_tokens': 100, 'input_token_details': {'cache_read': 0}}), HumanMessage(content='Remember my name?', additional_kwargs={}, response_metadata={}, id='8db110a8-be64-41c1-a393-fc87afdc34c4'), AIMessage(content='I will try my best to remember your name, Junaid! 😊  Is there anything else I can help you with? \\n', additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-ec966682-9e05-4d2c-b96b-b2be1eafca81-0', usage_metadata={'input_tokens': 108, 'output_tokens': 26, 'total_tokens': 134, 'input_token_details': {'cache_read': 0}})]}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef973cf-7cd9-6ed9-8004-f2265cf73a95'}}, metadata={'source': 'loop', 'writes': {'chatbot': {'messages': [AIMessage(content='I will try my best to remember your name, Junaid! 😊  Is there anything else I can help you with? \\n', additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-ec966682-9e05-4d2c-b96b-b2be1eafca81-0', usage_metadata={'input_tokens': 108, 'output_tokens': 26, 'total_tokens': 134, 'input_token_details': {'cache_read': 0}})]}}, 'step': 4, 'parents': {}}, created_at='2024-10-31T04:02:38.305429+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef973cf-79cf-6821-8003-6f438dfe217a'}}, tasks=())"
            ]
          },
          "metadata": {},
          "execution_count": 56
        }
      ],
      "source": [
        "snapshot = graph.get_state(config)\n",
        "snapshot"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c106bd09-f155-4e15-9120-c60c834106e5",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "c106bd09-f155-4e15-9120-c60c834106e5",
        "outputId": "8d063d76-09e0-49b8-c7cd-31a86839b46e"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "()"
            ]
          },
          "metadata": {},
          "execution_count": 57
        }
      ],
      "source": [
        "snapshot.next  # (since the graph ended this turn, `next` is empty. If you fetch a state from within a graph invocation, next tells which node will execute next)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "627f4998-6780-4cce-8f3c-9a5580888e3a",
      "metadata": {
        "id": "627f4998-6780-4cce-8f3c-9a5580888e3a"
      },
      "source": [
        "The snapshot above contains the current state values, corresponding config, and the `next` node to process. In our case, the graph has reached an `END` state, so `next` is empty.\n",
        "\n",
        "**Congratulations!** Your chatbot can now maintain conversation state across sessions thanks to LangGraph's checkpointing system. This opens up exciting possibilities for more natural, contextual interactions. LangGraph's checkpointing even handles **arbitrarily complex graph states**, which is much more expressive and powerful than simple chat memory.\n",
        "\n",
        "In the next part, we'll introduce human oversight to our bot to handle situations where it may need guidance or verification before proceeding.\n",
        "  \n",
        "Check out the code snippet below to review our graph from this section.\n",
        "\n",
        "<details>\n",
        "<summary>Full Code</summary>\n",
        "    <pre>\n",
        "\n",
        "```python\n",
        "from typing import Annotated\n",
        "\n",
        "from langchain_anthropic import ChatAnthropic\n",
        "from langchain_community.tools.tavily_search import TavilySearchResults\n",
        "from langchain_core.messages import BaseMessage\n",
        "from typing_extensions import TypedDict\n",
        "\n",
        "from langgraph.checkpoint.memory import MemorySaver\n",
        "from langgraph.graph import StateGraph\n",
        "from langgraph.graph.message import add_messages\n",
        "from langgraph.prebuilt import ToolNode\n",
        "\n",
        "\n",
        "class State(TypedDict):\n",
        "    messages: Annotated[list, add_messages]\n",
        "\n",
        "\n",
        "graph_builder = StateGraph(State)\n",
        "\n",
        "\n",
        "tool = TavilySearchResults(max_results=2)\n",
        "tools = [tool]\n",
        "llm = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n",
        "llm_with_tools = llm.bind_tools(tools)\n",
        "\n",
        "\n",
        "def chatbot(state: State):\n",
        "    return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
        "\n",
        "\n",
        "graph_builder.add_node(\"chatbot\", chatbot)\n",
        "\n",
        "tool_node = ToolNode(tools=[tool])\n",
        "graph_builder.add_node(\"tools\", tool_node)\n",
        "\n",
        "graph_builder.add_conditional_edges(\n",
        "    \"chatbot\",\n",
        "    tools_condition,\n",
        ")\n",
        "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
        "graph_builder.set_entry_point(\"chatbot\")\n",
        "graph = graph_builder.compile(checkpointer=memory)\n",
        "```\n",
        "</pre>\n",
        "</pre>\n",
        "</details>"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6f1da240-ec9b-441f-9d47-44c6dc85d540",
      "metadata": {
        "id": "6f1da240-ec9b-441f-9d47-44c6dc85d540"
      },
      "source": [
        "## Part 4: Human-in-the-loop\n",
        "\n",
        "Agents can be unreliable and may need human input to successfully accomplish tasks. Similarly, for some actions, you may want to require human approval before running to ensure that everything is running as intended.\n",
        "\n",
        "LangGraph supports `human-in-the-loop` workflows in a number of ways. In this section, we will use LangGraph's `interrupt_before` functionality to always break the tool node.\n",
        "\n",
        "First, start from our existing code. The following is copied from Part 3."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "5a81608a-373a-4339-b1c6-65b73a92b983",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "5a81608a-373a-4339-b1c6-65b73a92b983",
        "outputId": "1b366d58-a090-4105-a0bd-f8474d8a410e"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<langgraph.graph.state.StateGraph at 0x7ac951149c30>"
            ]
          },
          "metadata": {},
          "execution_count": 58
        }
      ],
      "source": [
        "from typing import Annotated\n",
        "\n",
        "from langchain_community.tools.tavily_search import TavilySearchResults\n",
        "from typing_extensions import TypedDict\n",
        "\n",
        "from langgraph.checkpoint.memory import MemorySaver\n",
        "from langgraph.graph import StateGraph, START\n",
        "from langgraph.graph.message import add_messages\n",
        "from langgraph.prebuilt import ToolNode, tools_condition\n",
        "\n",
        "memory = MemorySaver()\n",
        "\n",
        "\n",
        "class State(TypedDict):\n",
        "    messages: Annotated[list, add_messages]\n",
        "\n",
        "\n",
        "graph_builder = StateGraph(State)\n",
        "\n",
        "\n",
        "tool = TavilySearchResults(max_results=2)\n",
        "tools = [tool]\n",
        "llm_with_tools = llm.bind_tools(tools)\n",
        "\n",
        "\n",
        "def chatbot(state: State):\n",
        "    return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
        "\n",
        "\n",
        "graph_builder.add_node(\"chatbot\", chatbot)\n",
        "\n",
        "tool_node = ToolNode(tools=[tool])\n",
        "graph_builder.add_node(\"tools\", tool_node)\n",
        "\n",
        "graph_builder.add_conditional_edges(\n",
        "    \"chatbot\",\n",
        "    tools_condition,\n",
        ")\n",
        "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
        "graph_builder.add_edge(START, \"chatbot\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "813505b2-18c1-46e9-b891-20a34232808b",
      "metadata": {
        "id": "813505b2-18c1-46e9-b891-20a34232808b"
      },
      "source": [
        "Now, compile the graph, specifying to `interrupt_before` the `tools` node."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "b0883e32-1a39-4ce9-ae32-bbd66708fd84",
      "metadata": {
        "id": "b0883e32-1a39-4ce9-ae32-bbd66708fd84"
      },
      "outputs": [],
      "source": [
        "graph = graph_builder.compile(\n",
        "    checkpointer=memory,\n",
        "    # This is new!\n",
        "    interrupt_before=[\"tools\"],\n",
        "    # Note: can also interrupt __after__ tools, if desired.\n",
        "    # interrupt_after=[\"tools\"]\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "9f318020-ab7e-415b-a5e2-eddec6d9f3a6",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "9f318020-ab7e-415b-a5e2-eddec6d9f3a6",
        "outputId": "3c7f54f0-f121-47e2-9fc9-99c59b756730"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "I'm learning LangGraph. Could you do some research on it for me?\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "Tool Calls:\n",
            "  tavily_search_results_json (ed4916ab-0ab3-41d7-bafe-7f65330d9b79)\n",
            " Call ID: ed4916ab-0ab3-41d7-bafe-7f65330d9b79\n",
            "  Args:\n",
            "    query: LangGraph\n"
          ]
        }
      ],
      "source": [
        "user_input = \"I'm learning LangGraph. Could you do some research on it for me?\"\n",
        "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
        "# The config is the **second positional argument** to stream() or invoke()!\n",
        "events = graph.stream(\n",
        "    {\"messages\": [(\"user\", user_input)]}, config, stream_mode=\"values\"\n",
        ")\n",
        "for event in events:\n",
        "    if \"messages\" in event:\n",
        "        event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "39405637-13b1-40b1-a51e-6d60bf675ff1",
      "metadata": {
        "id": "39405637-13b1-40b1-a51e-6d60bf675ff1"
      },
      "source": [
        "Let's inspect the graph state to confirm it worked."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "9bb7af46-9b4f-4bb1-b8b9-e9ddf7dbc82c",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "9bb7af46-9b4f-4bb1-b8b9-e9ddf7dbc82c",
        "outputId": "1abf70bb-ded9-45fe-d1cd-df8b39812705"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "('tools',)"
            ]
          },
          "metadata": {},
          "execution_count": 61
        }
      ],
      "source": [
        "snapshot = graph.get_state(config)\n",
        "snapshot.next"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "89326046-2b11-4812-8b6d-8780306ec275",
      "metadata": {
        "id": "89326046-2b11-4812-8b6d-8780306ec275"
      },
      "source": [
        "**Notice** that unlike last time, the \"next\" node is set to **'tools'**. We've interrupted here! Let's check the tool invocation."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "3facda0a-e6ad-4b28-b627-753ad8c90c15",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "3facda0a-e6ad-4b28-b627-753ad8c90c15",
        "outputId": "6b7fec05-e1c0-4436-86b7-465e6466e5b9"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[{'name': 'tavily_search_results_json',\n",
              "  'args': {'query': 'LangGraph'},\n",
              "  'id': 'ed4916ab-0ab3-41d7-bafe-7f65330d9b79',\n",
              "  'type': 'tool_call'}]"
            ]
          },
          "metadata": {},
          "execution_count": 62
        }
      ],
      "source": [
        "existing_message = snapshot.values[\"messages\"][-1]\n",
        "existing_message.tool_calls"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a55a4c70-7226-4be0-8562-391f72bc1f2b",
      "metadata": {
        "id": "a55a4c70-7226-4be0-8562-391f72bc1f2b"
      },
      "source": [
        "This query seems reasonable. Nothing to filter here. The simplest thing the human can do is just let the graph continue executing. Let's do that below.\n",
        "\n",
        "Next, continue the graph! Passing in `None` will just let the graph continue where it left off, without adding anything new to the state."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "effb95d9-b7d5-40c5-9253-253d193b23b2",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "effb95d9-b7d5-40c5-9253-253d193b23b2",
        "outputId": "f26b2014-4cdb-446c-b214-3f6a45297bcd"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "Tool Calls:\n",
            "  tavily_search_results_json (ed4916ab-0ab3-41d7-bafe-7f65330d9b79)\n",
            " Call ID: ed4916ab-0ab3-41d7-bafe-7f65330d9b79\n",
            "  Args:\n",
            "    query: LangGraph\n",
            "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
            "Name: tavily_search_results_json\n",
            "\n",
            "[{\"url\": \"https://www.datacamp.com/tutorial/langgraph-tutorial\", \"content\": \"LangGraph is a library within the LangChain ecosystem that simplifies the development of complex, multi-agent large language model (LLM) applications. Learn how to use LangGraph to create stateful, flexible, and scalable systems with examples and code snippets.\"}, {\"url\": \"https://langchain-ai.github.io/langgraph/tutorials/\", \"content\": \"LangGraph is a framework for building language agents as graphs. Learn how to use LangGraph to create chatbots, code assistants, planning agents, reflection agents, and more with these notebooks.\"}]\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "LangGraph is a library within the LangChain ecosystem that simplifies the development of complex, multi-agent large language model (LLM) applications. It allows you to create stateful, flexible, and scalable systems with examples and code snippets. You can learn more about it at [https://www.datacamp.com/tutorial/langgraph-tutorial](https://www.datacamp.com/tutorial/langgraph-tutorial) and [https://langchain-ai.github.io/langgraph/tutorials/](https://langchain-ai.github.io/langgraph/tutorials/).\n"
          ]
        }
      ],
      "source": [
        "# `None` will append nothing new to the current state, letting it resume as if it had never been interrupted\n",
        "events = graph.stream(None, config, stream_mode=\"values\")\n",
        "for event in events:\n",
        "    if \"messages\" in event:\n",
        "        event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "21e78a97-474f-4709-b51d-9d5e8323e14c",
      "metadata": {
        "id": "21e78a97-474f-4709-b51d-9d5e8323e14c"
      },
      "source": [
        "Review this call's [LangSmith trace](https://smith.langchain.com/public/4d7f8757-9d3b-43b9-88b6-aeab0595bc4c/r) to see the exact work that was done in the above call. Notice that the state is loaded in the first step so that your chatbot can continue where it left off.\n",
        "\n",
        "**Congrats!** You've used an `interrupt` to add human-in-the-loop execution to your chatbot, allowing for human oversight and intervention when needed. This opens up the potential UIs you can create with your AI systems. Since we have already added a **checkpointer**, the graph can be paused **indefinitely** and resumed at any time as if nothing had happened.\n",
        "\n",
        "Next, we'll explore how to further customize the bot's behavior using custom state updates.\n",
        "\n",
        "Below is a copy of the code you used in this section. The only difference between this and the previous parts is the addition of the `interrupt_before` argument.\n",
        "\n",
        "<details>\n",
        "<summary>Full Code</summary>\n",
        "    <pre>\n",
        "\n",
        "```python\n",
        "from typing import Annotated\n",
        "\n",
        "from langchain_anthropic import ChatAnthropic\n",
        "from langchain_community.tools.tavily_search import TavilySearchResults\n",
        "from langchain_core.messages import BaseMessage\n",
        "from typing_extensions import TypedDict\n",
        "\n",
        "from langgraph.checkpoint.memory import MemorySaver\n",
        "from langgraph.graph import StateGraph\n",
        "from langgraph.graph.message import add_messages\n",
        "from langgraph.prebuilt import ToolNode, tools_condition\n",
        "\n",
        "\n",
        "class State(TypedDict):\n",
        "    messages: Annotated[list, add_messages]\n",
        "\n",
        "\n",
        "graph_builder = StateGraph(State)\n",
        "\n",
        "\n",
        "tool = TavilySearchResults(max_results=2)\n",
        "tools = [tool]\n",
        "llm = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n",
        "llm_with_tools = llm.bind_tools(tools)\n",
        "\n",
        "\n",
        "def chatbot(state: State):\n",
        "    return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
        "\n",
        "\n",
        "graph_builder.add_node(\"chatbot\", chatbot)\n",
        "\n",
        "tool_node = ToolNode(tools=[tool])\n",
        "graph_builder.add_node(\"tools\", tool_node)\n",
        "\n",
        "graph_builder.add_conditional_edges(\n",
        "    \"chatbot\",\n",
        "    tools_condition,\n",
        ")\n",
        "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
        "graph_builder.set_entry_point(\"chatbot\")\n",
        "\n",
        "memory = MemorySaver()\n",
        "graph = graph_builder.compile(\n",
        "    checkpointer=memory,\n",
        "    # This is new!\n",
        "    interrupt_before=[\"tools\"],\n",
        "    # Note: can also interrupt __after__ actions, if desired.\n",
        "    # interrupt_after=[\"tools\"]\n",
        ")\n",
        "```\n",
        "</pre>\n",
        "</details>"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6df38bc4-c177-4ccd-9ec2-83d32bf66722",
      "metadata": {
        "id": "6df38bc4-c177-4ccd-9ec2-83d32bf66722"
      },
      "source": [
        "## Part 5: Manually Updating the State\n",
        "\n",
        "In the previous section, we showed how to interrupt a graph so that a human could inspect its actions. This lets the human `read` the state, but if they want to change their agent's course, they'll need to have `write` access.\n",
        "\n",
        "Thankfully, LangGraph lets you **manually update state**! Updating the state lets you control the agent's trajectory by modifying its actions (even modifying the past!). This capability is particularly useful when you want to correct the agent's mistakes, explore alternative paths, or guide the agent towards a specific goal.\n",
        "\n",
        "We'll show how to update a checkpointed state below. As before, first, define your graph. We'll reuse the exact same graph as before."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "faa345c6-38a2-42e8-9035-9cf56f7bb5b1",
      "metadata": {
        "id": "faa345c6-38a2-42e8-9035-9cf56f7bb5b1"
      },
      "outputs": [],
      "source": [
        "from typing import Annotated\n",
        "\n",
        "from langchain_community.tools.tavily_search import TavilySearchResults\n",
        "from typing_extensions import TypedDict\n",
        "\n",
        "from langgraph.checkpoint.memory import MemorySaver\n",
        "from langgraph.graph import StateGraph, START\n",
        "from langgraph.graph.message import add_messages\n",
        "from langgraph.prebuilt import ToolNode, tools_condition\n",
        "\n",
        "\n",
        "class State(TypedDict):\n",
        "    messages: Annotated[list, add_messages]\n",
        "\n",
        "\n",
        "graph_builder = StateGraph(State)\n",
        "\n",
        "\n",
        "tool = TavilySearchResults(max_results=2)\n",
        "tools = [tool]\n",
        "llm_with_tools = llm.bind_tools(tools)\n",
        "\n",
        "\n",
        "def chatbot(state: State):\n",
        "    return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
        "\n",
        "\n",
        "graph_builder.add_node(\"chatbot\", chatbot)\n",
        "\n",
        "tool_node = ToolNode(tools=[tool])\n",
        "graph_builder.add_node(\"tools\", tool_node)\n",
        "\n",
        "graph_builder.add_conditional_edges(\n",
        "    \"chatbot\",\n",
        "    tools_condition,\n",
        ")\n",
        "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
        "graph_builder.add_edge(START, \"chatbot\")\n",
        "memory = MemorySaver()\n",
        "graph = graph_builder.compile(\n",
        "    checkpointer=memory,\n",
        "    # This is new!\n",
        "    interrupt_before=[\"tools\"],\n",
        "    # Note: can also interrupt **after** actions, if desired.\n",
        "    # interrupt_after=[\"tools\"]\n",
        ")\n",
        "\n",
        "user_input = \"I'm learning LangGraph. Could you do some research on it for me?\"\n",
        "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
        "# The config is the **second positional argument** to stream() or invoke()!\n",
        "events = graph.stream({\"messages\": [(\"user\", user_input)]}, config)\n",
        "for event in events:\n",
        "    if \"messages\" in event:\n",
        "        event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "a6b3bcae-dd04-49da-a4ef-e05634657faf",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "a6b3bcae-dd04-49da-a4ef-e05634657faf",
        "outputId": "9bb8a1d3-95e4-455d-dafc-9878eed530ec"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "Tool Calls:\n",
            "  tavily_search_results_json (6a3adc86-5cb4-45c8-9b70-0ded6b9ef6a2)\n",
            " Call ID: 6a3adc86-5cb4-45c8-9b70-0ded6b9ef6a2\n",
            "  Args:\n",
            "    query: LangGraph\n"
          ]
        }
      ],
      "source": [
        "snapshot = graph.get_state(config)\n",
        "existing_message = snapshot.values[\"messages\"][-1]\n",
        "existing_message.pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3bf55a26-8c12-477a-9e83-5011d36ac4ee",
      "metadata": {
        "id": "3bf55a26-8c12-477a-9e83-5011d36ac4ee"
      },
      "source": [
        "So far, all of this is an _exact repeat_ of the previous section. The LLM just requested to use the search engine tool and our graph was interrupted. If we proceed as before, the tool will be called to search the web.\n",
        "\n",
        "But what if the user wants to intercede? What if we think the chat bot doesn't need to use the tool?\n",
        "\n",
        "Let's directly provide the correct response!"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6a44bedc-ea91-4c22-976c-98b3d5a5e4a7",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "6a44bedc-ea91-4c22-976c-98b3d5a5e4a7",
        "outputId": "492ee6b8-0603-4668-fb21-0c0138d3ac3b"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "LangGraph is a library for building stateful, multi-actor applications with LLMs.\n",
            "\n",
            "\n",
            "Last 2 messages;\n",
            "[ToolMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', id='0d2d3897-a968-4aa5-b739-d7f79593a443', tool_call_id='6a3adc86-5cb4-45c8-9b70-0ded6b9ef6a2'), AIMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', additional_kwargs={}, response_metadata={}, id='f7a65d05-91cc-4502-9bb7-042063ef8a5c')]\n"
          ]
        }
      ],
      "source": [
        "from langchain_core.messages import AIMessage, ToolMessage\n",
        "\n",
        "answer = (\n",
        "    \"LangGraph is a library for building stateful, multi-actor applications with LLMs.\"\n",
        ")\n",
        "new_messages = [\n",
        "    # The LLM API expects some ToolMessage to match its tool call. We'll satisfy that here.\n",
        "    ToolMessage(content=answer, tool_call_id=existing_message.tool_calls[0][\"id\"]),\n",
        "    # And then directly \"put words in the LLM's mouth\" by populating its response.\n",
        "    AIMessage(content=answer),\n",
        "]\n",
        "\n",
        "new_messages[-1].pretty_print()\n",
        "graph.update_state(\n",
        "    # Which state to update\n",
        "    config,\n",
        "    # The updated values to provide. The messages in our `State` are \"append-only\", meaning this will be appended\n",
        "    # to the existing state. We will review how to update existing messages in the next section!\n",
        "    {\"messages\": new_messages},\n",
        ")\n",
        "\n",
        "print(\"\\n\\nLast 2 messages;\")\n",
        "print(graph.get_state(config).values[\"messages\"][-2:])"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "snapshot = graph.get_state(config)\n",
        "snapshot.next"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "HNeqW0SGv8Mx",
        "outputId": "951893ca-89c3-4e92-dc4b-e0ef4cb8b1d8"
      },
      "id": "HNeqW0SGv8Mx",
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "()"
            ]
          },
          "metadata": {},
          "execution_count": 68
        }
      ]
    },
    {
      "cell_type": "markdown",
      "id": "584de971-6b10-4931-986e-cc35f7adbb3d",
      "metadata": {
        "id": "584de971-6b10-4931-986e-cc35f7adbb3d"
      },
      "source": [
        "Now the graph is complete, since we've provided the final response message! Since state updates simulate a graph step, they even generate corresponding traces. Inspec the [LangSmith trace](https://smith.langchain.com/public/6d72aeb5-3bca-4090-8684-a11d5a36b10c/r) of the `update_state` call above to see what's going on.\n",
        "\n",
        "**Notice** that our new messages are _appended_ to the messages already in the state. Remember how we defined the `State` type?\n",
        "\n",
        "```python\n",
        "class State(TypedDict):\n",
        "    messages: Annotated[list, add_messages]\n",
        "```\n",
        "\n",
        "We annotated `messages` with the pre-built `add_messages` function. This instructs the graph to always append values to the existing list, rather than overwriting the list directly. The same logic is applied here, so the messages we passed to `update_state` were appended in the same way!\n",
        "\n",
        "The `update_state` function operates as if it were one of the nodes in your graph! By default, the update operation uses the node that was last executed, but you can manually specify it below. Let's add an update and tell the graph to treat it as if it came from the \"chatbot\"."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d16d95c3-b465-42ac-8015-26b669d45d1f",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "d16d95c3-b465-42ac-8015-26b669d45d1f",
        "outputId": "9fe0d80a-7acf-4ac9-c742-90a8fa6e3e9e"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'configurable': {'thread_id': '1',\n",
              "  'checkpoint_ns': '',\n",
              "  'checkpoint_id': '1ef973d7-4d00-6224-8003-e2025998a965'}}"
            ]
          },
          "metadata": {},
          "execution_count": 69
        }
      ],
      "source": [
        "graph.update_state(\n",
        "    config,\n",
        "    {\"messages\": [AIMessage(content=\"I'm an AI expert!\")]},\n",
        "    # Which node for this function to act as. It will automatically continue\n",
        "    # processing as if this node just ran.\n",
        "    as_node=\"chatbot\",\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5a1f0056-6b6f-425f-ac1a-0d4b0e9b85cc",
      "metadata": {
        "id": "5a1f0056-6b6f-425f-ac1a-0d4b0e9b85cc"
      },
      "source": [
        "Check out the [LangSmith trace](https://smith.langchain.com/public/2e4d92ca-c17c-49e0-92e5-3962390ded30/r) for this update call at the provided link. **Notice** from the trace that the graph continues into the `tools_condition` edge. We just told the graph to treat the update `as_node=\"chatbot\"`. If we follow the diagram below and start from the `chatbot` node, we naturally end up in the `tools_condition` edge and then `__end__` since our updated message lacks tool calls."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "f4009ba6-dc0b-4216-ab0c-fbb104616f73",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 308
        },
        "id": "f4009ba6-dc0b-4216-ab0c-fbb104616f73",
        "outputId": "995264fa-08c2-49e3-ac7f-f0dc95f504fc"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAEjATADASIAAhEBAxEB/8QAHQABAAMBAAMBAQAAAAAAAAAAAAQFBgcCAwgBCf/EAFgQAAEEAQIDAgYMCgYGBwkAAAEAAgMEBQYRBxIhEzEVFiJBVpQIFBc2UVR0k7LR0tQjMjVVYXF1gbTTMzRikaGxJCVzkrPECUJDUlNjciZERleDhJWi4f/EABoBAQEAAwEBAAAAAAAAAAAAAAABAgMEBQb/xAA1EQEAAQIBCAkDBAIDAAAAAAAAAQIRAxIxQVFSYaHRBBQhM3GBkbHBBROSFSIjYiQyouHw/9oADAMBAAIRAxEAPwD+qaIiAiIgIiICIiAiIgIiICIiAirc5mm4avHywvt2539lXqxEB0r/ANZ6AAbkuPQAE/oVT4kxZkdrqWU5qR2x9pv6Uov7LYu54/tScx/UOg3U0RbKrm0cVtrW0upMRA8tkylKNw7w6wwH/NePjVhfzxQ9ZZ9a8ItI4KFgZHhcdGwdzW1IwP8AJefirhfzPQ9WZ9Sy/h38DsPGrC/nih6yz608asL+eKHrLPrTxVwv5noerM+pPFXC/meh6sz6k/h38F7Dxqwv54oess+tPGrC/nih6yz608VcL+Z6HqzPqTxVwv5noerM+pP4d/A7Dxqwv54oess+tecOosVZeGQ5OnK8/wDVZYYT/gV4eKuF/M9D1Zn1Lwl0fgZ28smEx0jd99nVIyP8k/h38DsW6LMeKD8CO201KKJYPyZM9xpS9e7bYmI+YOZ0HeWv22VxhcxFm6XbxxyQPa4xzV5wBJDIPxmOAJG4+EEggggkEE4VUREZVM3j/wBnSyeiItSCIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiIMxidsvrbNXHgObihHjYB18hz2MnlPweUHwj/6f61p1mdMt9p6n1ZUcCHS24b7NxsDHJAyMdfP5cEi0y6Mb/aI3R7Qsi9Vq1DSrTWLErIK8LDJJLI4NaxoG5cSe4Add17VAz8NexgsjFapvyNWStI2WnG3mdOwtIdGBuNy4bjbfzrnRyjP+yp0ZDww1frDTtixqJmn6Htw120bUAn5+YQlrnQ9Y3uaR2rQ5gALidgSr2Dj/pGLh9j9XZG1ex+OtytqtbNiLrZnWCznLGQmHtXjYOIcGEENJB6LhOn9P6v1Fw24oaD05itVDQr9JSVcDV1nR9qXKl1zJWCjC9+zpYQwMAc7mDTs0PIWp1VrnUmptA6DOPwmu9O6eiutp6nix+InhzDI2Vd2CFgaZDEZuVr5IhvsDsQNyg6lY9kJw9q6LxurJdTQM0/kLvg6C4YZf6z5f4J7OTmjcOzfuHgbEbHqRvl8v7KjTmO4gaS0/HRzEtHO0bVz267CZBssRilbExnYe1+fynF+7jsGBrSekjSeMaR0JnG4/H036V1NDBHxgr52NmaglsTig+ruyzLKS/fZw8tznEtd0fs5dk4wzZDSXGvh5rRun8znsJTx2UxtvwHRfcnryTe13xOdEzd3KexeOYDYHbfbdB29F4sdzsa4AgEb7EbFeSAsxLtiOINcx7NhzFSQStG/WeHl5HfBuY3vBPf5DB126adZjLj25r7T8DNyalezckO3Ru4ZE0E/Ced+3/oP6N+jBzzE5rT7XjjZYadERc6CIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiIKLP4uyLtbM42NsuRqsdE6BzuUWYXEF0e/cHAtBaT0B3HQOcV6rMGnOJun7eMyFOpmsbIWsuYy/CHhrmuDwyWJw6OBDTs4eYH4FolT5nSWLzszbFmB8dxg5WW6sz4J2j4BIwh236N9v0LfFVNURTiaNK+LIM9jfwpicSzhxpdhILd24mAdCNiPxfOCQpGL9j/wzwmSqZHH6A03Sv1JWz17NfFwskikad2va4N3BBAIIVv4kStHLHqbPRtHm9sxu/xdGT/iniTY9Ks989D/AClft4e3wktGtqEWX8SbHpVnvnof5SzvEbDZPS3D3VGao6pzJu47F2rkAmlhLO0jic9vN+DHTcDfqE+3h7fCS0a3SkWNxekbdzGVLEmqs72ksLJHcssO25aCf+yUrxJselWe+eh/lJ9vD2+Elo1qKx7HPhXbsSzz8OtMTTSuL3yPxMBc5xO5JPL1JK8D7GzhO4knhvpYk95OIg+ytB4k2PSrPfPQ/wApBoeQkdrqXPSt335TZYz/ABYwH/FPt4e3wktGtMmu4nROJo46rXZBFDE2vQxVCMc7msAa2OKMbbNaNh5mtHUloBI89PYiepJbyF/s3ZW85pm7IlzImN37OJpPUhoJ67Ddznu2HNsPZhdL4zT7pJKVbaxIOWS1NI6aeQd4DpXkvcN9+hPnKtVjVVTTE00ac8ngIiLSgiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICxnGkgcHNdlxIb4Bv7kfJ3/pH+YWzWM407+45rvbYHwDf/GAI/q7+/fp/f0QaTAfkLHfJo/ohT1AwH5Cx3yaP6IU9AREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBYvjWN+DWvQXBo8AX/KcNwP9Hk6lbRYvjZt7jWvd+g8AX99hv8A+7yebzoNLgPyFjfk0f0Qp6gYD8hY35NH9EKegIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIqvUOejwFSN5ifZszv7GtWj6OmkIJ23PQAAEknuAJ/Qs67PavcSRjsIwH/qm7M7b9/ZDf+5dFGBXiRlRm3zZbNsixHh3WHxDB+tzfy08O6w+IYP1ub+WtnVa9cesFm3Xzh7ODj3Y4IcNBD4rS57H6lr3MTNdZbELaMj4do+ZpjeH8wdIdun9H59+nWPDusPiGD9bm/lrD8a9AZrjjw2y+j8zSwsNa8wGO1HYlc+vK07skaDH3gj94JHnTqteuPWCz89iZx8veyE0FYzkmk36ZxdOVlGrLJeFg3HMb+EcB2bOVrfJG/Xclw6cvXuC5Fwz01nOFWgsHpPDY3CNx+KrNrsc61NzSHvc934P8Zzi5x/SStN4d1h8Qwfrc38tOq1649YLNuixHh3WHxDB+tzfy08O6w+IYP1ub+WnVa9cesFm3RYpmf1cw8z8ZhpWjvYy7K0n9RMR/y+taTBZuDP48WoWviIe6KWCUAPhkadnMcB03BHeCQRsQSCCdWJgV4cZU5t03LLFERaEEREBERAREQEREBERAREQEREBERAREQEREBERAREQYzWx/9qNJjzdtZP7+wd//AFWCr9be+nSX+2s/8EqwXqR3VHh8ys6BEVPqHV2J0rLiI8pb9qvy15mNpDs3v7Ww9r3tZ5IPLu2N53dsOnf1CxRcIii5TK08HjbWRyNqGjQqxOmns2HhkcUbRu5znHoAACSSqJSKkyGtcLi8np/H2bwZbz8j4saxsb3Cw5kTpnbOAIbtGxzt3EA7bDr0V2oCKFmc1Q05ireTylyDH46pGZZ7VmQRxxMHe5zj0AUxrg9ocDuCNwVR+qFw9P8AperB5hl+g/8Ata5/zJU1QeHv9c1d+2P+UrK1d1X4R7wsaWxREXloIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgxmtvfTpL/bWf+CVYKv1t76dJf7az/wAEqwXqR3VHh8ys6HI+Pucykd3QOl8fl7Ona2p857Qu5Wm4MnjibBLN2UTyPIfI6NrA4dR126rGcauHDcDieGmCr6m1HOy3ryo4X72SdZuVwalkFsU0gLgOhIJ3ILiQR027lrTQ2B4iYGTDajxsWUx0j2ydlKS0te07texzSHMcPM5pBHwqhxHA/ReDp0K1TESCOjlGZqB016xNILjYzE2Vz3yFzyGOLdnEjbbp0C1zF0cXuaos6Fi4oaKuZ3VWZrVctiKWCfXyX+tXTXY2OFZtuTq1peHeW47tY52x3AWWyh1LJwm9kTo3U1zJiLB4eC/TimzsuQsQCWtLIY3WyyN8jCYQSxwI2c5pLmnr9NZ3g/pDUrtQuyeGbafn31pMg508rXSPrgCB7SHAxuYANnR8p3677r0ac4JaJ0ocscbg2RnL1BSyRmsSz+3ovL6T9o93au2keOd+7tjtvt0WOTI4zq7h9VbkeAeCgzWfjr3L9uZ17wvPJcZvi5XFsc73Oexp222aRsCdtu9UuU1fqvTOSz/Datq3K2sY3WWHwkWpLM4kyFSrdrmaWHtyOsjXMEbXu3cO2HnA27YPY2cOxgqeHODndQpWHWqrH5S259eQsEfNG8y87NmNAAaQBt02VrU4JaHo6Gt6Pi07W8Xrkhns1ZHPe6aUkO7V8rnGR0m7WkPLuYco2PQJkyPmzjnUuYHTHGjQPjBnM5gqelaueryZDIyz2Kc7pZWPgdMTzvjeI2v5Hkjv8xX1forTlbSunatCpcyF+ADtBPk78t2Y83X+llc5xHwDfYDuVNpzgzozSuCzWHoYOM0c00syQtzSWpLjSws5ZZZXOe8BpIALugJ22VpobQOE4cYTwRgK01XH9oZRFPbmskHla3o6V7nAbNaA0HYbdAsoi03GhUHh7/XNXftj/lKynKDw9/rmrv2x/wApWWyruq/CPeFjS2KIi8tBERAREQEREBERAREQEREBERAREQEREBERAREQEREGM1t76dJf7az/AMEqwVdqmyM9fxLMLG7IX6lqcixE4GpA6NhZJFYkB3YXF4aA0OcH7HkIY/aMcpn2HY6PyLyO8x2qhaf1bzA/4BephzFeHTETHZ2dsxGmZ0+LLOukVJ4Wz3oZlfWqX89PC2e9DMr61S/nrPI/tH5RzLLtFSeFs96GZX1ql/PVPrHiNNoDTGR1FqDTWRxuGx8Rms2pLFQhjd9u4TEkkkAAAkkgBMj+0flHMs2aLK4DWd/VGEoZjFaWyVzG34GWa1hlmntJG4AtPWfcdD3HqFP8LZ70MyvrVL+emR/aPyjmWXaKk8LZ70MyvrVL+enhbPehmV9apfz0yP7R+Ucyy7UHh7/XNXftj/lKyiMyWoJTyt0jfice51i3VDP3lsrj/cCvZh8fn9FVcrbnij1A21NFZNPGx9nYjkcQyXZ0kgbIxrAwtHku2Y7o4kBa8SYow6omYvOqYnTfQZm4RQMZnMfmZb0VK5FZlozmrajY7d0EoAdyPHe08rmuG/eHNI6EFT15jEREQEREBERAREQEREBERAREQEREBERAREQEX4TsNz3KgbmrmdsMZh42tx7ZbNa1fnDo3xvjHKOxY5m0n4TfyiQ3aM7c24QS85qOrg4JuZst26yEzx42k0SWp2hzW+RHvuRzOaC47NHMC4gdV6JMXk8nkJzeutr46KzDNTgx7nxyuDBu4Tyb+U1zz+I0AbMAcXBzmiTh8BWw8cThzW77a8dWXJWg11qwxhcW9o8Ab+U97ths0F7tgN1ZoPRTpV8dWZXqV4qtdm/LFCwMa3c7nYDp1JJ/eveiICIiAvmX2fHDLW3FLg++lpjJYrH4TGtny2bbfmljlnjgj542R8kbg7ueSHEDcM/d9NLE8b5o6/BjXskpaI24C+TzkgH/AEd/Tcdf7uqDn3sN+GeueEPCSDS+s7+JyUNaTtcXNjLEshZBIOYxvD4mbcriSNt/xj3bBd3UXFV3VMXTgeNnxQsY4fpDQFKQEREBERBAyOCpZW1Rs2YS6xSm7evKx7mOY7YtPVpG4IcQWncHfqCqmtPmtNwVIMgZdQVWMsPs5WKJrJ4w3yoga8Y/CEt3aTGAS4N2Zs48ulRBFxeTq5rHVb9KZtipZjbNFK3fZzHDcHr17j51KVFmdMe2p7mSxVkYrPy1RVZfLDLHytfzsEkPMGyAEuHmcA94a5vMSvZHqSOvkHU8pG3FSSWm1aMliePkyDjEZPwPlblwDJN2EBw7Nx2LdnELlERAREQEREBERAREQEREBERAREQF4TTMrxPlle2ONjS5z3HYNA6kkrzWe1JBLk8xhMcat51EyuuT26tjso2GEtMccu3lOa9zgeUbA9mQ7du7SHhFUdrKKG3cDm4OQVblOlJBNWs87SZOacOLSASYtoXMaWmM8+/NyM0iIgIiICIiAiIgLn/E5x1RfwuiK/luyM7LuTLT/Q4+CRr382x/7V4ZCB5w+Qj8Q7aTVuq6+k8fDK+J9y9bmFWhj4f6W5YcCWxs/c1znOPRjGPe4hrXERdEaWs4KG5fy1hl3UWUkbPkLMW/ZtIbyshhB6thjHRo6bkveRzyPJDTIiICIiAiIgIiIC9c9eKywMmjZKwOa8Ne0OAc0hzT184IBB8xAK9iIM5Xsv0gyCrkbrpsSG8rMvkrMYe2V8wbFA87N5t+0YxjurnFuzyXkF+jXqs1obtaWvYiZPBKwxyRStDmvaRsWkHoQR02VNpy/PHcvYa9Ys3b1Lab25NUELJoZHPMfKW+Q4tDSx22x3aCWtDm7hfIiICIiAiIgIipcxrbT2n7QrZPOY7H2SObsbNpjH7fDyk77LOmiqubUxeVtddIst7qWjvSnEeux/WnupaO9KcR67H9a29XxtifSVyZ1NSiy3upaO9KcR67H9ae6lo70pxHrsf1p1fG2J9JMmdTUost7qWjvSnEeux/WnupaO9KcR67H9adXxtifSTJnU1KxHEHUWB0Xm9L5zP2oMZX7efHjJXMjHUr1+0hdKe0D3ND+Y12tAG5BIPduVO91LR3pTiPXY/rXwX/ANIHwPwvEHUtDiBofLY7JZe7JDRzFGtbY+R+wDIrIG/c1oax3wANPmcU6vjbE+kmTOp/Q3A6gxeqcTXymFyVPL4ywCYbtCdk8MoBLTyvaSDsQR0PeCrBc04c5/QfDrQWn9MUtUYYVsTRiptLbkY5yxoBd397juf3rRe6lo70pxHrsf1p1fG2J9JMmdTUost7qWjvSnEeux/WnupaO9KcR67H9adXxtifSTJnU1KLLe6lo70pxHrsf1p7qWjvSnEeux/WnV8bYn0kyZ1NSqfVGp6uk8YLViOe1NLIIKtGowPsW5nA8sUTSQC47EkkhrWtc97msa5wzmoeNekMFiZrcWZp5SduzIqdK1G6WZ7js1o3cGtG5G7nENaNy4gAlVOmNU6XivnP6g1bgrupJozG3sbsboMdC7lLq1YnY8pLGl8jgHSuaCQ1rY443V8bYn0kyZ1NPpjS9tmTfqLULoLGo5oTXY2s5z6+PrlwcYIC4AnctYZJS1rpXMaSGtZHHHqllvdS0d6U4j12P617IeJmkbEgZHqbEPcdgALsfnOw8/wkD96dXxtifSUtOppURFzoIiICIiAiIgIiICz2eJp6n03cHhiYSyT490NHZ1Rgkj7XtrLfMGmsGNeOrXTbdz3baFZ3WjfwGHfyZd5ZlKp2w52d1fy7zfDAN93/ANkfoQaJERAREQEREELNXHY/D3rTAC+CCSVoPwtaSP8AJZHSVSOtgKUgHNPZiZPPM7q+aRzQXPcT1JJP7u7uC0+qvexmPkc30Cs9pr3uYr5JF9AL0MDswp8V0LJERZoIiICIiAiIgIiICIiAiIgIiIC/HsbIwte0OaRsWuG4K/UQROHbxBDnMZGSKmMyJrVo9ukUboIZgxv9lplIA7gAAAAAFrljuH35T1n+2GfwNRbFc3Se9ny4xCznERFyoIiICIiAiIgLN655PaWK535dg8K09vA345PbN2Ev/kf+J/Y3WkWd1s4tp4vYZg75SmP9S/j/ANM3+l/8j/xP7HMg0SIiAiIgIiIKvVXvYzHyOb6BWe0173MV8ki+gFodVe9jMfI5voFZ7TXvcxXySL6AXo4Pcz4/C6FkiIskEREBERByT2SWtdVaI0zpuxpSGCS1c1HjaM/b2RDzRyWGN7Lcxv2EhPIXAbtDiRuRsvZnOL+pKeocdpTEaLgzWsXYsZbJUm5gQ1KMJeY2j2w6HeRznNcGjs29GknlCuuNegclxE0ZFTwtqrUzVDJU8tRfeDjXdNXnZK1knL5Qa7lIJAJG++x7lk7+hOJFXWFXXWHGlm6muYnwRl8XbsWfaJbHO+SCWGZsXOXNEjg5rmAHfoRtusJvceFL2SUmrK+mqej9KzZrU2XrWrc+Ku3W02Y2OtN2E/by8r+om/BtDWnm236BVNji7a11qbhLPTF/T8z9UZHEZvDmwfIngp2eeGQsPLK0Pa17T3HyTsD3eGnPY/ar4Yzacz+lcnh8tquCneqZluZ7WvVve2rPtp743Rte6Msl3DQWndp2Ox6r20/Y/anw2M01lamXxVvWVPVVrVGRNhkkdKw+1HJFNFHtu9obHIAwkHcs6gb9Mf3aRreF/GTJ8T9RZiKrpeOpp7HXbWPdkX5WN1ps0MhYRLV5A6LmIJb5RO2xIG4XU1xTE8J9W2uOlHW+Vj0ziK9FtyGSfAduLeXhkHLBHba5ob+DGzt+Z/lN6co6Ltazi+kERFkCIiAiIgg8PvynrP8AbDP4Gotisdw+/Kes/wBsM/gai2K5uld55R7QsiIi5UEREBEWR1lxNxWj5Parmy5DJlocKVUAuaD3F7js1g/Wdz12B2W3Cwq8arIw4vI1yLiVjjfqKZxdBicZVae5ks0kxH6yA3/Jen3adVfE8P8A7sv2l60fR+lzoj1hfN3NfH/svvZjZPgFrfHact6HuXMfIa2VpZehn/ahtiN4L4Xs9rP2bzNLXM5jzNIPTmXTfdp1V8Tw/wDuy/aXLuO+APshcfgampqGNaMPfbdhlrCQPc3p2kJJJ8h4Dd9uvkt2PRX9G6Xqj1g830lwe11kOJnDLT+qsngXaat5av7a8GOs+2DFG5x7M8/IzfmZyP8AxRtzbddt1slwmHjHqevEyKLH4WKJjQ1jGMlDWgdAAOboF5+7Tqr4nh/92X7Sfo3S9UesHm7mi4YONOqt+tLDkf8Apl+0rbEcdZ45WtzWFDIPPZx0plLevnjcAdh3+SSf0fDhX9I6XTF8m/hMFnXUUTFZWnm8fDeoWGWqkw3ZLGdweuxH6CCCCD1BBB6hS148xNM2nOir1V72Mx8jm+gVntNe9zFfJIvoBaHVXvYzHyOb6BWe0173MV8ki+gF6GD3M+PwuhZIvReqm7RsVxNJXM0boxNC7lezcbczT5iO8FV3ixB8cyXr0v2lZmUXCKn8WIPjmS9el+0nixB8cyXr0v2lLzqFwip/FiD45kvXpftJ4sQfHMl69L9pLzqFwip/FiD45kvXpftJ4sQfHMl69L9pLzqFwip/FiD45kvXpftJ4sQfHMl69L9pLzqFwip/FiD45kvXpftJ4sQfHMl69L9pLzqFwip/FiD45kvXpftJ4sQfHMl69L9pLzqFwip/FiD45kvXpftJ4sQfHMl69L9pLzqFwij0aLMfCYmSTSgnm5p5XSO/vcSVIVEHh9+U9Z/thn8DUWxWO4fflPWf7YZ/A1FsVz9K7zyj2hZERFyoIiIMrxI1c7R2mnWa4a7IWJG1qjX93aOBPMfhDWtc7bz8u3nXAgHFz3ySPnmkcXyTSu5nyOPe5x85XROPUr/DWlod/wACYrkpHm5x2Ab/AIPeueL7z6Pg04fRoxIz1X4Tb4JzCIi9xgIuAZeTWfEHXes62Lnnrx4SyylVjg1BJjuw3ha8SviZBIJeYuJBedthsB0JNlSxGoNUa9v4XP6iydOxU0zQmnZhbz68Ptwuna+VvLse9vd0B6cwOw24o6TebU0znt78ldtVdgtRY/U1WexjbHtmGCzLUkdyOZyyxvLJG7OA7nAjfuPm3XDtG6jzPFabQuGyWcv4yvLpkZi3LjLBrT3p+1EWxkbsQ0bFxDdty8b9FtPY91TR0XlKxmlsmHPZOPtp3c0km1p45nHzk7bk/CmH0j7tcRTHZMcuY6ciIu1F/oHVcmjNRROLyMVflbFciJ8lrj5LJgPM4Hla4+dvfvyN2+h18mZsA4a9v3dg/qR3eSV9UYmaSxi6csw2lfCxzwf+8WjdfH/XMGmmqjGjPN4nysz0I2qvexmPkc30Cs9pr3uYr5JF9ALQ6q97GY+RzfQKz2mve5ivkkX0AvIwe5nx+F0LJERZIIsnxX1q/hxw21JqeOq+5Ji6MllsUbWuJIHQlrnsDgO8gOBIBDd3EA5LO+yCpaO8I18tgc1cnwdenJm7mNrRe1aZnYCHeXMHEAnq1oc8DrsR1WMzEDrKLmFLjFYPEPXOLyGEnx+ldLwROs5+SSDso5OwNiUyfhufl7J0Jbyxk9Xc3L03r5/ZN4Cjjspdv4HUOOip4nw3AyzWhEt6qZGxtdEwSlzXOc9gDJRG4793Q7MqB19FzzK8Y2Ye7gsfPpHUTsvm32RRxsbKpmeyBjHukce35I2kPaBzuaQTs4NJC/ZeNuGgwOXyslDJNjxuch08+ARxmWa3JJBEBHtJs5ofYDSSR1Y/YHYbrwOhIuYQeyAxE2XgrPwWdgxs2cl06zNSQQ+0zdZM+Hk6SmTldIwtD+Tl3IBIO4EvhDxLyvEluoLNzTdrD46plLNOhblkgcyzHDKYX/iTPdziSOXc8obty8pd1KXgdERZefilourqAYKbV+BizhmbWGMkycLbJlcQGx9kXc3MSQA3bc7hZ/jfrjK6Kx2lW4WOzPkMrqGnR9r04o5Jp4RzTTxsEmzQXRQyN5iW8vNvzN23FvA6Qi5fD7ITBW6NIVMTmreftXbOPbpuOCIX2TV9jOH80gia1gcwl5k5CHs2ceYLN6l415HWfue0NF1cxSZqqe1JPkIIqTrNSvW5mzBjZ5DHziXswXbPbyFxbzktCmVA7oi5tiuOeFyOZxVKLH5d2Lyd6TF0NRyQxCjctRtkLmMIf2nXspAHmMMcW+S47jeJjfZEYTI6Rg1N4DztbD3ZWVsZJNBCZMnO+R0bIq8TZS8ucWkguDW8vlc2wJDKgdURZbQHECrxAqZWSHHXsTaxd52Ou0sh2RlhmbHHIRvFJIxw5ZWHdrj3kHYghalUQeH35T1n+2GfwNRbFY7h9+U9Z/thn8DUWxXP0rvPKPaFkREXKgiIgwHGXTU2a05BeqROmt4ub2x2bBu6SItLZGj9xD9vOWAedcXY9srGvY4PY4bhzTuCPhC+p1ynWvB2WazLf02+vC6Q80mNn8iJzvO6NwB5CevkkEE+dvUn6f6V9QowafsY02jRPwZ3z/LQ1+ZXmPO6bbHueUPwtgkDzbn22Nz+5fj6HEEuPLndNBu/QHC2CQPW10KxpTU1NxbPpnJNcO/smsmH7ixxXp8A570by/qp+tfSxVgT2xif8v8AtMmXPsvwj07q2eHIajxsF3Mmu2C1ZpPmqssAd4cxsnlM79mvLth03Wir6YxlTN2MvDVDMjYrR05Zg93lRRlxY3l32Gxe7qBv1/Ur/wAA570by/qp+tPAOe9G8v6qfrWcVdHibxNN/GDJlgbfBzSF3DYbFy4jarhmllAxWZo5a7T3hsrXh+x84Luu3VflfROQ0hRix2iJsRhMWHyTSV8hTntkyvcXOc1wsM2BJ7uv6Nu5b/wDnvRvL+qn608A570by/qp+tY/42eJpid0xBkywPg/iH+ftM//AISx97V1pyvqKA2PD1/GXgeXsfB1GSty9/Nzc80nNv022222Pfv00gwOfP8A8N5f1U/WrbEcNtVZuVrfBgxEJ77OQe07dfNGxxcT+g8v61jOL0fC/dViR+V+FzJlUYfASatzdPCxNLm2Hc1lzTt2dcdZHH9fRg/S8L6bAAAAGwHmWf0ZoihoqhJFV5p7U5DrNyX+kmI32H6Gjc7NHQbk9SXE6FfF/Uumx0zEjI/1pzfMstyr1V72Mx8jm+gVntNe9zFfJIvoBaHVXvYzHyOb6BWe0173MV8ki+gFpwe5nx+DQl3rElSlYnirS3ZYo3PZWgLBJKQNwxpe5rQT3DmcBuepA6rI+P8Anf8A5a6o9ZxX31bVFUc11Xj8lxg0zf0vd09mdI1bJgklvZD2lPHIyOxE98IbBae7eRjXN3I2AJPUgNPoz3BHw/jtaVZs1yu1RnKeUsS+1dzHXriq32qBz9Q5tYjn83ak8p269RRS2scptcDpslV4l4m9n2z6d1q6aaSuylyW6k0kEUJcJ+0LXta2JvK0xjbzkgKFjPY9x1NFyYCSfT9Iz5ShetT6f04zGttRVrEc3ZSMbK7dzzGQX77AOOzPMuxomTAzFnRPtviXj9WyXOYUcTYxkFIxfimaaKSSXn5u8iBjduX4evmXPvcGyda9XMuq2S6dqapm1Z4OjxJNmeR0sk4hfN2x5g2R7S0tjB2YAQehHaES0DgXBzhFqHI6O0Rb1flRHSqT+MTNOtxhrTxX5nyT7WpXSOLzHJO88oZH5QHNvyro3CPQGR4Z6XOBt5uHN04JpX05G0TXlYx8j5CJT2jxI/med3gM3/7q26JERAy8/DnFWNQDMvt54WxM2fs49Q5BlbmaQQPa4nEXL06s5OU9dwdyqviPw8y+r8/pbM4fP1sLc0/JYnhZbxxuRSSyxdiHOaJYz5Mb5gAD3vB32aQ7eIraBwTLexQoXXYq8Mjjsrm4JLs2Qs6nwkeTr35bT43yyGDnjEbmmJgYWu8lo5SHAla3GaRvScbKuT8HClp7TunXYmlIGMjjlnnliklMMbT5LGMgibvsBu4gb7FdORTJgcZ017H29hMXgcTZ1WLmI0xHP4v12Y7sn15XxSRRzWH9qe3fGyV4byiMEuJIJ2Il6o9j3Q1Dwq0Xo1tqofFU1H1Jcjjm3Ks7oYHQHtq7nAPa5kj9xzggkEO3C62iZMCj0TpeHRumKOJhgx0AgaeZuJoNpVuYkklkLS4MHXu3J+ElXiIqIPD78p6z/bDP4Gotisdw+/Kes/2wz+BqLYrn6V3nlHtCyIiLlQREQEREBERAREQEREBERAREQV2o4X2NPZSKNpdI+rK1rR5yWEBZrS72yaaxLmndrqkJB+EcgW2WTtcPm9vI/GZvJYOF7i81aYgfCHHqS1ssT+Xc9dmkDck7dV24OJTFM0VTbSuiySigeIGQ9M838xS+7p4gZD0zzfzFL7ut98PbjjyLb09FA8QMh6Z5v5il93TxAyHpnm/mKX3dL4e3HHkW3p6KB4gZD0zzfzFL7uniBkPTPN/MUvu6Xw9uOPItvT0UDxAyHpnm/mKX3dPEDIemeb+Ypfd0vh7cceRbenooHiBkPTPN/MUvu6eIGQ9M838xS+7pfD2448i29PRQPEDIemeb+Ypfd08QMh6Z5v5il93S+Htxx5Ft6eigeIGQ9M838xS+7p4gZD0zzfzFL7ul8PbjjyLb09FA8QMh6Z5v5il93TxAyHpnm/mKX3dL4e3HHkW3p6KB4gZD0zzfzFL7uvJmgbm5Eurs1Mw97ezqM36/C2AEfuPnUvh7cceRbe/eH7CL2rZQd2S5cFp2PmqVmH/9muH7lr1ExWKq4THw0qUIgrRAhrdy4kkklxJ3LnEkkuJJJJJJJKlrixq4xK5qjNy7Ce0REWlBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQf/2Q==\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        }
      ],
      "source": [
        "from IPython.display import Image, display\n",
        "\n",
        "try:\n",
        "    display(Image(graph.get_graph().draw_mermaid_png()))\n",
        "except Exception:\n",
        "    # This requires some extra dependencies and is optional\n",
        "    pass"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "96cd4ffa-8fb2-4bd6-bef9-564cbfe7e3ab",
      "metadata": {
        "id": "96cd4ffa-8fb2-4bd6-bef9-564cbfe7e3ab"
      },
      "source": [
        "Inspect the current state as before to confirm the checkpoint reflects our manual updates."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d420e813-a8c7-415d-ab31-5298d42491e4",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "d420e813-a8c7-415d-ab31-5298d42491e4",
        "outputId": "8c88f8bf-03a5-4ef9-b95f-4b77c78acd32"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "[ToolMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', id='0d2d3897-a968-4aa5-b739-d7f79593a443', tool_call_id='6a3adc86-5cb4-45c8-9b70-0ded6b9ef6a2'), AIMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', additional_kwargs={}, response_metadata={}, id='f7a65d05-91cc-4502-9bb7-042063ef8a5c'), AIMessage(content=\"I'm an AI expert!\", additional_kwargs={}, response_metadata={}, id='fba74b9e-fde3-43fa-8c40-6f34cf26abdf')]\n",
            "()\n"
          ]
        }
      ],
      "source": [
        "snapshot = graph.get_state(config)\n",
        "print(snapshot.values[\"messages\"][-3:])\n",
        "print(snapshot.next)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "380222f4-65fa-4962-afe6-6a715fadb2de",
      "metadata": {
        "id": "380222f4-65fa-4962-afe6-6a715fadb2de"
      },
      "source": [
        "**Notice**: that we've continued to add AI messages to the state. Since we are acting as the `chatbot` and responding with an AIMessage that doesn't contain `tool_calls`, the graph knows that it has entered a finished state (`next` is empty).\n",
        "\n",
        "#### What if you want to **overwrite** existing messages?\n",
        "\n",
        "The [`add_messages`](https://langchain-ai.github.io/langgraph/reference/graphs/?h=add+messages#add_messages) function we used to annotate our graph's `State` above controls how updates are made to the `messages` key. This function looks at any message IDs in the new `messages` list. If the ID matches a message in the existing state, [`add_messages`](https://langchain-ai.github.io/langgraph/reference/graphs/?h=add+messages#add_messages) overwrites the existing message with the new content.\n",
        "\n",
        "As an example, let's update the tool invocation to make sure we get good results from our search engine! First, start a new thread:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "9fc99c7e-b61d-4aec-9c62-042798185ec3",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "9fc99c7e-b61d-4aec-9c62-042798185ec3",
        "outputId": "e7ccb980-6950-45d6-b3ff-11bc3b4645bf"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "I'm learning LangGraph. Could you do some research on it for me?\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "Tool Calls:\n",
            "  tavily_search_results_json (b4dc1eda-f5d3-4260-af9d-263d7d0fa438)\n",
            " Call ID: b4dc1eda-f5d3-4260-af9d-263d7d0fa438\n",
            "  Args:\n",
            "    query: LangGraph\n"
          ]
        }
      ],
      "source": [
        "user_input = \"I'm learning LangGraph. Could you do some research on it for me?\"\n",
        "config = {\"configurable\": {\"thread_id\": \"2\"}}  # we'll use thread_id = 2 here\n",
        "events = graph.stream(\n",
        "    {\"messages\": [(\"user\", user_input)]}, config, stream_mode=\"values\"\n",
        ")\n",
        "for event in events:\n",
        "    if \"messages\" in event:\n",
        "        event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8b019fc6-7826-4291-9178-6cecb5d7b3d0",
      "metadata": {
        "id": "8b019fc6-7826-4291-9178-6cecb5d7b3d0"
      },
      "source": [
        "**Next,** let's update the tool invocation for our agent. Maybe we want to search for human-in-the-loop workflows in particular."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "7215533a-b7e2-4b2d-bc1d-5122b1d06b8b",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "7215533a-b7e2-4b2d-bc1d-5122b1d06b8b",
        "outputId": "5709a7ea-60f9-477f-f082-798bdc03c789"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Original\n",
            "Message ID run-0593108e-cf64-4cd5-8081-cd2a0f2a888d-0\n",
            "{'name': 'tavily_search_results_json', 'args': {'query': 'LangGraph'}, 'id': 'b4dc1eda-f5d3-4260-af9d-263d7d0fa438', 'type': 'tool_call'}\n",
            "Updated\n",
            "{'name': 'tavily_search_results_json', 'args': {'query': 'LangGraph human-in-the-loop workflow'}, 'id': 'b4dc1eda-f5d3-4260-af9d-263d7d0fa438', 'type': 'tool_call'}\n",
            "Message ID run-0593108e-cf64-4cd5-8081-cd2a0f2a888d-0\n",
            "\n",
            "\n",
            "Tool calls\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[{'name': 'tavily_search_results_json',\n",
              "  'args': {'query': 'LangGraph human-in-the-loop workflow'},\n",
              "  'id': 'b4dc1eda-f5d3-4260-af9d-263d7d0fa438',\n",
              "  'type': 'tool_call'}]"
            ]
          },
          "metadata": {},
          "execution_count": 73
        }
      ],
      "source": [
        "from langchain_core.messages import AIMessage\n",
        "\n",
        "snapshot = graph.get_state(config)\n",
        "existing_message = snapshot.values[\"messages\"][-1]\n",
        "print(\"Original\")\n",
        "print(\"Message ID\", existing_message.id)\n",
        "print(existing_message.tool_calls[0])\n",
        "\n",
        "new_tool_call = existing_message.tool_calls[0].copy()\n",
        "new_tool_call[\"args\"][\"query\"] = \"LangGraph human-in-the-loop workflow\"\n",
        "new_message = AIMessage(\n",
        "    content=existing_message.content,\n",
        "    tool_calls=[new_tool_call],\n",
        "    # Important! The ID is how LangGraph knows to REPLACE the message in the state rather than APPEND this messages\n",
        "    id=existing_message.id,\n",
        ")\n",
        "\n",
        "print(\"Updated\")\n",
        "print(new_message.tool_calls[0])\n",
        "print(\"Message ID\", new_message.id)\n",
        "\n",
        "graph.update_state(config, {\"messages\": [new_message]})\n",
        "\n",
        "print(\"\\n\\nTool calls\")\n",
        "graph.get_state(config).values[\"messages\"][-1].tool_calls"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "680f0ebd-ebce-4de6-8a9b-37d3d4ef0234",
      "metadata": {
        "id": "680f0ebd-ebce-4de6-8a9b-37d3d4ef0234"
      },
      "source": [
        "**Notice** that we've modified the AI's tool invocation to search for \"LangGraph human-in-the-loop workflow\" instead of the simple \"LangGraph\".\n",
        "\n",
        "Check out the [LangSmith trace](https://smith.langchain.com/public/cd7c09a6-758d-41d4-8de1-64ab838b2338/r) to see the state update call - you can see our new message has successfully updated the previous AI message.\n",
        "\n",
        "Resume the graph by streaming with an input of `None` and the existing config."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "03a09bfc-3d90-4e54-878f-22e3cb28a418",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "03a09bfc-3d90-4e54-878f-22e3cb28a418",
        "outputId": "3f1a318e-6eee-44bc-e2e2-0d8a62e5f935"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "Tool Calls:\n",
            "  tavily_search_results_json (b4dc1eda-f5d3-4260-af9d-263d7d0fa438)\n",
            " Call ID: b4dc1eda-f5d3-4260-af9d-263d7d0fa438\n",
            "  Args:\n",
            "    query: LangGraph human-in-the-loop workflow\n",
            "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
            "Name: tavily_search_results_json\n",
            "\n",
            "[{\"url\": \"https://www.youtube.com/watch?v=9BPCV5TYPmg\", \"content\": \"In this video, I'll show you how to handle persistence with LangGraph, enabling a unique Human-in-the-Loop workflow. This approach allows a human to grant an\"}, {\"url\": \"https://medium.com/@kbdhunga/implementing-human-in-the-loop-with-langgraph-ccfde023385c\", \"content\": \"Implementing a Human-in-the-Loop (HIL) framework in LangGraph with the Streamlit app provides a robust mechanism for user engagement and decision-making. By incorporating breakpoints and\"}]\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "LangGraph is a tool for building human-in-the-loop applications. It allows humans to interact with the system and provide feedback, which can be used to improve the system's performance. This is especially useful in applications where the system needs to learn from human input, such as natural language processing or machine learning. \n",
            "\n",
            "LangGraph is often used in conjunction with Streamlit, a web framework for building interactive data applications. This combination allows users to build applications that are both powerful and easy to use.\n",
            "\n",
            "Here are some examples of how LangGraph can be used:\n",
            "\n",
            "* **Building a chatbot:** A chatbot that uses LangGraph can learn from user interactions and improve its responses over time.\n",
            "* **Creating a personalized recommendation system:** A recommendation system that uses LangGraph can learn from user feedback and provide more relevant recommendations.\n",
            "* **Developing a machine learning model:** A machine learning model that uses LangGraph can be trained on a dataset of human-labeled data.\n",
            "\n",
            "If you are interested in learning more about LangGraph, I recommend checking out the following resources:\n",
            "\n",
            "* **LangGraph documentation:** https://langgraph.readthedocs.io/en/latest/\n",
            "* **LangGraph GitHub repository:** https://github.com/langgraph/langgraph\n",
            "* **LangGraph YouTube channel:** https://www.youtube.com/channel/UC-y42wM_00D71j5KzM8e1Jg\n"
          ]
        }
      ],
      "source": [
        "events = graph.stream(None, config, stream_mode=\"values\")\n",
        "for event in events:\n",
        "    if \"messages\" in event:\n",
        "        event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "090b680b-f53f-4af2-a432-45f8c5a10779",
      "metadata": {
        "id": "090b680b-f53f-4af2-a432-45f8c5a10779"
      },
      "source": [
        "Check out the [trace](https://smith.langchain.com/public/2d633326-14ad-4248-a391-2757d01851c4/r/6464f2f2-edb4-4ef3-8f48-ee4e249f2ad0) to see the tool call and later LLM response. **Notice** that now the graph queries the search engine using our updated query term - we were able to manually override the LLM's search here!\n",
        "\n",
        "All of this is reflected in the graph's checkpointed memory, meaning if we continue the conversation, it will recall all the _modified_ state."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "11d5b934-6d8b-4f52-a3bc-b3daa7207e00",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "11d5b934-6d8b-4f52-a3bc-b3daa7207e00",
        "outputId": "bf07c7ce-b184-4ecf-a9fb-51400f9e3c08"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "Remember what I'm learning about?\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "You are learning about LangGraph, a tool for building human-in-the-loop applications.\n"
          ]
        }
      ],
      "source": [
        "events = graph.stream(\n",
        "    {\n",
        "        \"messages\": (\n",
        "            \"user\",\n",
        "            \"Remember what I'm learning about?\",\n",
        "        )\n",
        "    },\n",
        "    config,\n",
        "    stream_mode=\"values\",\n",
        ")\n",
        "for event in events:\n",
        "    if \"messages\" in event:\n",
        "        event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a5166e1b-96a6-4ac0-88a1-bf32a422134a",
      "metadata": {
        "id": "a5166e1b-96a6-4ac0-88a1-bf32a422134a"
      },
      "source": [
        "**Congratulations!** You've used `interrupt_before` and `update_state` to manually modify the state as a part of a human-in-the-loop workflow. Interruptions and state modifications let you control how the agent behaves. Combined with persistent checkpointing, it means you can `pause` an action and `resume` at any point. Your user doesn't have to be available when the graph interrupts!\n",
        "\n",
        "The graph code for this section is identical to previous ones. The key snippets to remember are to add `.compile(..., interrupt_before=[...])` (or `interrupt_after`) if you want to explicitly pause the graph whenever it reaches a node. Then you can use `update_state` to modify the checkpoint and control how the graph should proceed."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d88d4c9e-65c8-4093-a6c2-c261475f7c07",
      "metadata": {
        "id": "d88d4c9e-65c8-4093-a6c2-c261475f7c07"
      },
      "source": [
        "## Part 6: Customizing State\n",
        "\n",
        "So far, we've relied on a simple state (it's just a list of messages!). You can go far with this simple state, but if you want to define complex behavior without relying on the message list, you can add additional fields to the state. In this section, we will extend our chat bot with a new node to illustrate this.\n",
        "\n",
        "In the examples above, we involved a human deterministically: the graph __always__ interrupted whenever an tool was invoked. Suppose we wanted our chat bot to have the choice of relying on a human.\n",
        "\n",
        "One way to do this is to create a passthrough \"human\" node, before which the graph will always stop. We will only execute this node if the LLM invokes a \"human\" tool. For our convenience, we will include an \"ask_human\" flag in our graph state that we will flip if the LLM calls this tool.\n",
        "\n",
        "Below, define this new graph, with an updated `State`"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "3cf7e042-1718-4625-ae30-a9917f595449",
      "metadata": {
        "id": "3cf7e042-1718-4625-ae30-a9917f595449"
      },
      "outputs": [],
      "source": [
        "from typing import Annotated\n",
        "\n",
        "from langchain_community.tools.tavily_search import TavilySearchResults\n",
        "from typing_extensions import TypedDict\n",
        "\n",
        "from langgraph.checkpoint.memory import MemorySaver\n",
        "from langgraph.graph import StateGraph, START\n",
        "from langgraph.graph.message import add_messages\n",
        "from langgraph.prebuilt import ToolNode, tools_condition\n",
        "\n",
        "\n",
        "class State(TypedDict):\n",
        "    messages: Annotated[list, add_messages]\n",
        "    # This flag is new\n",
        "    ask_human: bool"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e87f2cb8-c066-4b54-acc4-e8c7399c5f3d",
      "metadata": {
        "id": "e87f2cb8-c066-4b54-acc4-e8c7399c5f3d"
      },
      "source": [
        "Next, define a schema to show the model to let it decide to request assistance."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7bd3d704-5bee-4872-8d12-992bc970c158",
      "metadata": {
        "id": "7bd3d704-5bee-4872-8d12-992bc970c158"
      },
      "source": [
        "<div class=\"admonition note\">\n",
        "    <p class=\"admonition-title\">Using Pydantic with LangChain</p>\n",
        "    <p>\n",
        "        This notebook uses Pydantic v2 <code>BaseModel</code>, which requires <code>langchain-core >= 0.3</code>. Using <code>langchain-core < 0.3</code> will result in errors due to mixing of Pydantic v1 and v2 <code>BaseModels</code>.\n",
        "    </p>\n",
        "</div>"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "e5192e54-6a28-42fe-a8a7-62d45d61f994",
      "metadata": {
        "id": "e5192e54-6a28-42fe-a8a7-62d45d61f994"
      },
      "outputs": [],
      "source": [
        "from pydantic import BaseModel\n",
        "\n",
        "\n",
        "class RequestAssistance(BaseModel):\n",
        "    \"\"\"Escalate the conversation to an expert. Use this if you are unable to assist directly or if the user requires support beyond your permissions.\n",
        "\n",
        "    To use this function, relay the user's 'request' so the expert can provide the right guidance.\n",
        "    \"\"\"\n",
        "\n",
        "    request: str"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2b19c61b-2087-463b-adf8-96dbc193f41c",
      "metadata": {
        "id": "2b19c61b-2087-463b-adf8-96dbc193f41c"
      },
      "source": [
        "Next, define the chatbot node. The primary modification here is flip the `ask_human` flag if we see that the chat bot has invoked the `RequestAssistance` flag."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "fa59b266-14e5-4c75-8b3d-54fac28e8290",
      "metadata": {
        "id": "fa59b266-14e5-4c75-8b3d-54fac28e8290"
      },
      "outputs": [],
      "source": [
        "tool = TavilySearchResults(max_results=2)\n",
        "tools = [tool]\n",
        "\n",
        "# We can bind the llm to a tool definition, a pydantic model, or a json schema\n",
        "llm_with_tools = llm.bind_tools(tools + [RequestAssistance])\n",
        "\n",
        "\n",
        "def chatbot(state: State):\n",
        "    response = llm_with_tools.invoke(state[\"messages\"])\n",
        "    ask_human = False\n",
        "    if (\n",
        "        response.tool_calls\n",
        "        and response.tool_calls[0][\"name\"] == RequestAssistance.__name__\n",
        "    ):\n",
        "        ask_human = True\n",
        "    return {\"messages\": [response], \"ask_human\": ask_human}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "04ca0f57-2519-49c2-9499-888b5a884897",
      "metadata": {
        "id": "04ca0f57-2519-49c2-9499-888b5a884897"
      },
      "source": [
        "Next, create the graph builder and add the chatbot and tools nodes to the graph, same as before."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "3f4464d2-288b-4689-aaf0-329a55dcb85c",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "3f4464d2-288b-4689-aaf0-329a55dcb85c",
        "outputId": "0aa463a1-3e4a-4dea-8f45-2a44ffb0e32e"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<langgraph.graph.state.StateGraph at 0x7ac95114bb80>"
            ]
          },
          "metadata": {},
          "execution_count": 79
        }
      ],
      "source": [
        "graph_builder = StateGraph(State)\n",
        "\n",
        "graph_builder.add_node(\"chatbot\", chatbot)\n",
        "graph_builder.add_node(\"tools\", ToolNode(tools=[tool]))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7f7a0ff3-b671-45c8-8157-ce5db411d370",
      "metadata": {
        "id": "7f7a0ff3-b671-45c8-8157-ce5db411d370"
      },
      "source": [
        "Next, create the \"human\" `node`. This `node` function is mostly a placeholder in our graph that will trigger an interrupt. If the human does __not__ manually update the state during the `interrupt`, it inserts a tool message so the LLM knows the user was requested but didn't respond. This node also unsets the `ask_human` flag so the graph knows not to revisit the node unless further requests are made."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "1d70b5a4-ce50-47dc-aa43-ffb5c48c46fc",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "1d70b5a4-ce50-47dc-aa43-ffb5c48c46fc",
        "outputId": "cfd96626-fd1b-4ead-ad68-8835e536ef30"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<langgraph.graph.state.StateGraph at 0x7ac95114bb80>"
            ]
          },
          "metadata": {},
          "execution_count": 80
        }
      ],
      "source": [
        "from langchain_core.messages import AIMessage, ToolMessage\n",
        "\n",
        "\n",
        "def create_response(response: str, ai_message: AIMessage):\n",
        "    return ToolMessage(\n",
        "        content=response,\n",
        "        tool_call_id=ai_message.tool_calls[0][\"id\"],\n",
        "    )\n",
        "\n",
        "\n",
        "def human_node(state: State):\n",
        "    new_messages = []\n",
        "    if not isinstance(state[\"messages\"][-1], ToolMessage):\n",
        "        # Typically, the user will have updated the state during the interrupt.\n",
        "        # If they choose not to, we will include a placeholder ToolMessage to\n",
        "        # let the LLM continue.\n",
        "        new_messages.append(\n",
        "            create_response(\"No response from human.\", state[\"messages\"][-1])\n",
        "        )\n",
        "    return {\n",
        "        # Append the new messages\n",
        "        \"messages\": new_messages,\n",
        "        # Unset the flag\n",
        "        \"ask_human\": False,\n",
        "    }\n",
        "\n",
        "\n",
        "graph_builder.add_node(\"human\", human_node)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d56e5c65-f7b7-48bd-b0b5-fc8e590eca7d",
      "metadata": {
        "id": "d56e5c65-f7b7-48bd-b0b5-fc8e590eca7d"
      },
      "source": [
        "Next, define the conditional logic. The `select_next_node` will route to the `human` node if the flag is set. Otherwise, it lets the prebuilt `tools_condition` function choose the next node.\n",
        "\n",
        "Recall that the `tools_condition` function simply checks to see if the `chatbot` has responded with any `tool_calls` in its response message. If so, it routes to the `action` node. Otherwise, it ends the graph."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "586a0d07-8303-47f4-b3cf-3bdd043e762b",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "586a0d07-8303-47f4-b3cf-3bdd043e762b",
        "outputId": "b88e85eb-8780-48dc-f58b-9d05136a4d4a"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<langgraph.graph.state.StateGraph at 0x7ac95114bb80>"
            ]
          },
          "metadata": {},
          "execution_count": 81
        }
      ],
      "source": [
        "def select_next_node(state: State):\n",
        "    if state[\"ask_human\"]:\n",
        "        return \"human\"\n",
        "    # Otherwise, we can route as before\n",
        "    return tools_condition(state)\n",
        "\n",
        "\n",
        "graph_builder.add_conditional_edges(\n",
        "    \"chatbot\",\n",
        "    select_next_node,\n",
        "    {\"human\": \"human\", \"tools\": \"tools\", END: END},\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "66cd0bb1-b13e-477e-a08a-a7e657e2c19e",
      "metadata": {
        "id": "66cd0bb1-b13e-477e-a08a-a7e657e2c19e"
      },
      "source": [
        "Finally, add the simple directed edges and compile the graph. These edges instruct the graph to **always** flow from node `a`->`b` whenever `a` finishes executing."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "84101737-0048-4635-9f68-45b0c508b6b6",
      "metadata": {
        "id": "84101737-0048-4635-9f68-45b0c508b6b6"
      },
      "outputs": [],
      "source": [
        "# The rest is the same\n",
        "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
        "graph_builder.add_edge(\"human\", \"chatbot\")\n",
        "graph_builder.add_edge(START, \"chatbot\")\n",
        "memory = MemorySaver()\n",
        "graph = graph_builder.compile(\n",
        "    checkpointer=memory,\n",
        "    # We interrupt before 'human' here instead.\n",
        "    interrupt_before=[\"human\"],\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7f855593-8690-4a18-9ef8-7f3ccdc335bf",
      "metadata": {
        "id": "7f855593-8690-4a18-9ef8-7f3ccdc335bf"
      },
      "source": [
        "If you have the visualization dependencies installed, you can see the graph structure below:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "b3220ae2-cba0-4447-96d1-eb0be4684e59",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 308
        },
        "id": "b3220ae2-cba0-4447-96d1-eb0be4684e59",
        "outputId": "f104b216-af92-4ff6-f872-b4fe159691c2"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "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fp6km0/mspg8vpeLCsuYii+22vZZdfIRMGAmNpZKDzu83zT16INwREQFV8ftiNeZGizZtfJVW5BjB6JmOEcx/sIdB0HpDj3lWhVgjx3iUxzNy3HYlzJDt03sTNLRv69qxJHo3HrC6ML+6J0W/wDOdlhZ0RFzoIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiCv5mhYx2VbncfD4xL2QguVWnzp4WkuaWejtGFztgehDnDpuCPDJ4jS/FPT3iuToY/UmIdIHOrXYGzRtkb6HMePNe3fqCAQe8BWNQmV0bistdN18MlXIEAG7SmfXmcB3BzmEF4HqduOp6LfFVNcRFerX1XTpVNvg3cKWBwbw40u0PGzgMTB1G4Ox831gf4Lv0/wM4d6TzFbLYXQ+n8Tk6xJhuU8bFFLGSC08rmtBG4JH9hKkDoiYABmp88xo9HbxO/5mMlPImx7VZ766H+Er+Hh7/KS0bVoRVfyJse1We+uh/hKp8VcfldG6ByuYx2qcwblYRmMTywlnnSsadx2Y9Dj6U/Dw9/lJaNrVF+EBwII3B7wqx5E2ParPfXQ/wk8ibHtVnvrof4Sfh4e/yktG1Xv/Vr4T/9W2lf+EQfur9Pg2cJ3Ek8N9LEnvJxMBJ/8KsHkTY9qs99dD/CX6NDueOWfUWdsM67t8bEW4P542tP+BTIw9/lJaNrvy2eq4PsKMDG2MjK3arjoSA9wHTcj+jGPS89B/aQD5adwz8TWnksvZNkbkvjFuZgPK6QgDZu/Xla1rWj8zRv1JXswunMbp6OVmPqMrmUh0su5dJKR0Be9xLnnb0uJKkljVVTEZNGj9zyERFpQREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFnvH8gcIs/uSBtB3f9vH+cLQlnvH/f8EWf2232g+Ntt/p4/Wg0JERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFnnhAjfhDqDqG9IOpHT/TxrQ1nnhA7fgh1Bv0G0Ho3/18aDQ0REBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQERE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D4czPsZm/raX3lPhzM+xmb+tpfeVf0TvUbkc+pfgqGExGQymbrZXJU3YuKm17a1SSRr5XPeNi95Y4tADdwACT5xJI7lb0Rc2JiTiTeSZuIiLUgiIgIiICIiAiIgIiICIiAiIgIiII7UcL7GnspFG0ukfVla1o9JLCAq1pd7ZNNYlzTu11SEg+scgV2VTtcPm9vI/GZvJYOF7i81aYgfCHHqS1ssT+Xc9dmkDck7dV24OJTFM0VTbWuqzpRcHkBkPbPN/UUvu6eQGQ9s839RS+7rffD3459C3F3ouDyAyHtnm/qKX3dPIDIe2eb+opfd0vh78c+hbi70XB5AZD2zzf1FL7unkBkPbPN/UUvu6Xw9+OfQtxd6Lg8gMh7Z5v6il93TyAyHtnm/qKX3dL4e/HPoW4u9FweQGQ9s839RS+7p5AZD2zzf1FL7ul8Pfjn0LcXei4PIDIe2eb+opfd08gMh7Z5v6il93S+Hvxz6FuLvRcHkBkPbPN/UUvu6eQGQ9s839RS+7pfD3459C3F3ouDyAyHtnm/qKX3dPIDIe2eb+opfd0vh78c+hbi70XB5AZD2zzf1FL7uvJmgbm5Eurs1Mw97ezqM36+tsAI/uPpUvh78c+hbi/eH7CL2rZQd2S5cFp2PoqVmH/wATXD+5W9cmKxVXCY+GlShEFaIENbuXEkkkuJO5c4kklxJJJJJJJXWuLGrjErmqNHTMTnERFpQREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERB//9k=\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        }
      ],
      "source": [
        "from IPython.display import Image, display\n",
        "\n",
        "try:\n",
        "    display(Image(graph.get_graph().draw_mermaid_png()))\n",
        "except Exception:\n",
        "    # This requires some extra dependencies and is optional\n",
        "    pass"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a3b73851-810e-4466-89d8-37fba87e8494",
      "metadata": {
        "id": "a3b73851-810e-4466-89d8-37fba87e8494"
      },
      "source": [
        "The chat bot can either request help from a human (chatbot->select->human), invoke the search engine tool (chatbot->select->action), or directly respond (chatbot->select->__end__). Once an action or request has been made, the graph will transition back to the `chatbot` node to continue operations.\n",
        "\n",
        "Let's see this graph in action. We will request for expert assistance to illustrate our graph."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c1955d79-a1e4-47d0-ba79-b45bd5752a23",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "c1955d79-a1e4-47d0-ba79-b45bd5752a23",
        "outputId": "9ce86a2c-c6d4-4750-d950-dbefc430c6d0"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "I need some expert guidance for building this AI agent. Could you request assistance for me?\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "Tool Calls:\n",
            "  RequestAssistance (25cc6e79-9808-4b63-95df-ba1a6c35b5d8)\n",
            " Call ID: 25cc6e79-9808-4b63-95df-ba1a6c35b5d8\n",
            "  Args:\n",
            "    request: I need some expert guidance for building this AI agent.\n"
          ]
        }
      ],
      "source": [
        "user_input = \"I need some expert guidance for building this AI agent. Could you request assistance for me?\"\n",
        "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
        "# The config is the **second positional argument** to stream() or invoke()!\n",
        "events = graph.stream(\n",
        "    {\"messages\": [(\"user\", user_input)]}, config, stream_mode=\"values\"\n",
        ")\n",
        "for event in events:\n",
        "    if \"messages\" in event:\n",
        "        event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b3945ea4-8dbd-4e14-ae2a-34da7f05a0c1",
      "metadata": {
        "id": "b3945ea4-8dbd-4e14-ae2a-34da7f05a0c1"
      },
      "source": [
        "**Notice:** the LLM has invoked the \"`RequestAssistance`\" tool we provided it, and the interrupt has been set. Let's inspect the graph state to confirm."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "5320ba05-5696-4194-8278-5385c571264d",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "5320ba05-5696-4194-8278-5385c571264d",
        "outputId": "166757a5-bbac-4665-cd04-765d88fb86cd"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "('human',)"
            ]
          },
          "metadata": {},
          "execution_count": 85
        }
      ],
      "source": [
        "snapshot = graph.get_state(config)\n",
        "snapshot.next"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ed2dd02e-f0a6-4f63-a7d6-e49ecf40db21",
      "metadata": {
        "id": "ed2dd02e-f0a6-4f63-a7d6-e49ecf40db21"
      },
      "source": [
        "The graph state is indeed **interrupted** before the `'human'` node. We can act as the \"expert\" in this scenario and manually update the state by adding a new ToolMessage with our input.\n",
        "\n",
        "Next, respond to the chatbot's request by:\n",
        "1. Creating a `ToolMessage` with our response. This will be passed back to the `chatbot`.\n",
        "2. Calling `update_state` to manually update the graph state."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "2cbac924-61ce-4282-9b1c-77f9090ea1f5",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "2cbac924-61ce-4282-9b1c-77f9090ea1f5",
        "outputId": "9739095d-9404-418e-9b89-bfd1cabd62aa"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'configurable': {'thread_id': '1',\n",
              "  'checkpoint_ns': '',\n",
              "  'checkpoint_id': '1ef973e8-c910-6f1c-8002-8a22235d093a'}}"
            ]
          },
          "metadata": {},
          "execution_count": 86
        }
      ],
      "source": [
        "ai_message = snapshot.values[\"messages\"][-1]\n",
        "human_response = (\n",
        "    \"We, the experts are here to help! We'd recommend you check out LangGraph to build your agent.\"\n",
        "    \" It's much more reliable and extensible than simple autonomous agents.\"\n",
        ")\n",
        "tool_message = create_response(human_response, ai_message)\n",
        "graph.update_state(config, {\"messages\": [tool_message]})"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "79492363-7fc6-4ec7-977d-9030648029bc",
      "metadata": {
        "id": "79492363-7fc6-4ec7-977d-9030648029bc"
      },
      "source": [
        "You can inspect the state to confirm our response was added."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "4b986c66-1c65-4da8-a404-db7e28f8364e",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "4b986c66-1c65-4da8-a404-db7e28f8364e",
        "outputId": "462dbde6-2725-4628-debb-566d9b0ff659"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[HumanMessage(content='I need some expert guidance for building this AI agent. Could you request assistance for me?', additional_kwargs={}, response_metadata={}, id='9ba65a95-0c10-4993-853e-308f7d9017d3'),\n",
              " AIMessage(content='', additional_kwargs={'function_call': {'name': 'RequestAssistance', 'arguments': '{\"request\": \"I need some expert guidance for building this AI agent.\"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-476f0073-0a84-4caf-9357-878624870e0b-0', tool_calls=[{'name': 'RequestAssistance', 'args': {'request': 'I need some expert guidance for building this AI agent.'}, 'id': '25cc6e79-9808-4b63-95df-ba1a6c35b5d8', 'type': 'tool_call'}], usage_metadata={'input_tokens': 173, 'output_tokens': 24, 'total_tokens': 197, 'input_token_details': {'cache_read': 0}}),\n",
              " ToolMessage(content=\"We, the experts are here to help! We'd recommend you check out LangGraph to build your agent. It's much more reliable and extensible than simple autonomous agents.\", id='99935d37-ff8a-48b8-8cd7-2a3eb12e0e7c', tool_call_id='25cc6e79-9808-4b63-95df-ba1a6c35b5d8')]"
            ]
          },
          "metadata": {},
          "execution_count": 87
        }
      ],
      "source": [
        "graph.get_state(config).values[\"messages\"]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ea6b8616-de10-44d6-a8f0-3ac73c3c3680",
      "metadata": {
        "id": "ea6b8616-de10-44d6-a8f0-3ac73c3c3680"
      },
      "source": [
        "Next, **resume** the graph by invoking it with `None` as the inputs."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6b32914d-4d60-491f-8e11-1e6867e38ffd",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "6b32914d-4d60-491f-8e11-1e6867e38ffd",
        "outputId": "ec5248ce-9b14-4063-94fb-6bf9a48c5b6a"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
            "\n",
            "We, the experts are here to help! We'd recommend you check out LangGraph to build your agent. It's much more reliable and extensible than simple autonomous agents.\n",
            "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
            "\n",
            "We, the experts are here to help! We'd recommend you check out LangGraph to build your agent. It's much more reliable and extensible than simple autonomous agents.\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "OK. I've requested assistance from an expert. They recommend checking out LangGraph for building your AI agent. It's apparently more reliable and extensible than simple autonomous agents.\n"
          ]
        }
      ],
      "source": [
        "events = graph.stream(None, config, stream_mode=\"values\")\n",
        "for event in events:\n",
        "    if \"messages\" in event:\n",
        "        event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "graph.get_state(config).values[\"messages\"]"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "ikr_vFL8yMt9",
        "outputId": "24324fac-e70e-4d1d-f835-6a30cb632cdf"
      },
      "id": "ikr_vFL8yMt9",
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[HumanMessage(content='I need some expert guidance for building this AI agent. Could you request assistance for me?', additional_kwargs={}, response_metadata={}, id='9ba65a95-0c10-4993-853e-308f7d9017d3'),\n",
              " AIMessage(content='', additional_kwargs={'function_call': {'name': 'RequestAssistance', 'arguments': '{\"request\": \"I need some expert guidance for building this AI agent.\"}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-476f0073-0a84-4caf-9357-878624870e0b-0', tool_calls=[{'name': 'RequestAssistance', 'args': {'request': 'I need some expert guidance for building this AI agent.'}, 'id': '25cc6e79-9808-4b63-95df-ba1a6c35b5d8', 'type': 'tool_call'}], usage_metadata={'input_tokens': 173, 'output_tokens': 24, 'total_tokens': 197, 'input_token_details': {'cache_read': 0}}),\n",
              " ToolMessage(content=\"We, the experts are here to help! We'd recommend you check out LangGraph to build your agent. It's much more reliable and extensible than simple autonomous agents.\", id='99935d37-ff8a-48b8-8cd7-2a3eb12e0e7c', tool_call_id='25cc6e79-9808-4b63-95df-ba1a6c35b5d8'),\n",
              " AIMessage(content=\"OK. I've requested assistance from an expert. They recommend checking out LangGraph for building your AI agent. It's apparently more reliable and extensible than simple autonomous agents. \\n\", additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-fe0fcfeb-5770-4e3e-88d3-3ff210efa56e-0', usage_metadata={'input_tokens': 248, 'output_tokens': 36, 'total_tokens': 284, 'input_token_details': {'cache_read': 0}})]"
            ]
          },
          "metadata": {},
          "execution_count": 89
        }
      ]
    },
    {
      "cell_type": "markdown",
      "id": "48e0559b-d653-4dab-8928-b001004d14cb",
      "metadata": {
        "id": "48e0559b-d653-4dab-8928-b001004d14cb"
      },
      "source": [
        "**Notice** that the chat bot has incorporated the updated state in its final response. Since **everything** was checkpointed, the \"expert\" human in the loop could perform the update at any time without impacting the graph's execution.\n",
        "\n",
        "**Congratulations!** you've now added an additional node to your assistant graph to let the chat bot decide for itself whether or not it needs to interrupt execution. You did so by updating the graph `State` with a new `ask_human` field and modifying the interruption logic when compiling the graph. This lets you dynamically include a human in the loop while maintaining full **memory** every time you execute the graph.\n",
        "\n",
        "We're almost done with the tutorial, but there is one more concept we'd like to review before finishing that connects `checkpointing` and `state updates`.\n",
        "\n",
        "This section's code is reproduced below for your reference.\n",
        "\n",
        "<details>\n",
        "<summary>Full Code</summary>\n",
        "    <pre>\n",
        "\n",
        "```python\n",
        "from typing import Annotated\n",
        "\n",
        "from langchain_anthropic import ChatAnthropic\n",
        "from langchain_community.tools.tavily_search import TavilySearchResults\n",
        "from langchain_core.messages import BaseMessage\n",
        "# NOTE: you must use langchain-core >= 0.3 with Pydantic v2\n",
        "from pydantic import BaseModel\n",
        "from typing_extensions import TypedDict\n",
        "\n",
        "from langgraph.checkpoint.memory import MemorySaver\n",
        "from langgraph.graph import StateGraph\n",
        "from langgraph.graph.message import add_messages\n",
        "from langgraph.prebuilt import ToolNode, tools_condition\n",
        "\n",
        "\n",
        "class State(TypedDict):\n",
        "    messages: Annotated[list, add_messages]\n",
        "    # This flag is new\n",
        "    ask_human: bool\n",
        "\n",
        "\n",
        "class RequestAssistance(BaseModel):\n",
        "    \"\"\"Escalate the conversation to an expert. Use this if you are unable to assist directly or if the user requires support beyond your permissions.\n",
        "\n",
        "    To use this function, relay the user's 'request' so the expert can provide the right guidance.\n",
        "    \"\"\"\n",
        "\n",
        "    request: str\n",
        "\n",
        "\n",
        "tool = TavilySearchResults(max_results=2)\n",
        "tools = [tool]\n",
        "llm = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n",
        "# We can bind the llm to a tool definition, a pydantic model, or a json schema\n",
        "llm_with_tools = llm.bind_tools(tools + [RequestAssistance])\n",
        "\n",
        "\n",
        "def chatbot(state: State):\n",
        "    response = llm_with_tools.invoke(state[\"messages\"])\n",
        "    ask_human = False\n",
        "    if (\n",
        "        response.tool_calls\n",
        "        and response.tool_calls[0][\"name\"] == RequestAssistance.__name__\n",
        "    ):\n",
        "        ask_human = True\n",
        "    return {\"messages\": [response], \"ask_human\": ask_human}\n",
        "\n",
        "\n",
        "graph_builder = StateGraph(State)\n",
        "\n",
        "graph_builder.add_node(\"chatbot\", chatbot)\n",
        "graph_builder.add_node(\"tools\", ToolNode(tools=[tool]))\n",
        "\n",
        "\n",
        "def create_response(response: str, ai_message: AIMessage):\n",
        "    return ToolMessage(\n",
        "        content=response,\n",
        "        tool_call_id=ai_message.tool_calls[0][\"id\"],\n",
        "    )\n",
        "\n",
        "\n",
        "def human_node(state: State):\n",
        "    new_messages = []\n",
        "    if not isinstance(state[\"messages\"][-1], ToolMessage):\n",
        "        # Typically, the user will have updated the state during the interrupt.\n",
        "        # If they choose not to, we will include a placeholder ToolMessage to\n",
        "        # let the LLM continue.\n",
        "        new_messages.append(\n",
        "            create_response(\"No response from human.\", state[\"messages\"][-1])\n",
        "        )\n",
        "    return {\n",
        "        # Append the new messages\n",
        "        \"messages\": new_messages,\n",
        "        # Unset the flag\n",
        "        \"ask_human\": False,\n",
        "    }\n",
        "\n",
        "\n",
        "graph_builder.add_node(\"human\", human_node)\n",
        "\n",
        "\n",
        "def select_next_node(state: State):\n",
        "    if state[\"ask_human\"]:\n",
        "        return \"human\"\n",
        "    # Otherwise, we can route as before\n",
        "    return tools_condition(state)\n",
        "\n",
        "\n",
        "graph_builder.add_conditional_edges(\n",
        "    \"chatbot\",\n",
        "    select_next_node,\n",
        "    {\"human\": \"human\", \"tools\": \"tools\", \"__end__\": \"__end__\"},\n",
        ")\n",
        "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
        "graph_builder.add_edge(\"human\", \"chatbot\")\n",
        "graph_builder.set_entry_point(\"chatbot\")\n",
        "memory = MemorySaver()\n",
        "graph = graph_builder.compile(\n",
        "    checkpointer=memory,\n",
        "    interrupt_before=[\"human\"],\n",
        ")\n",
        "```\n",
        "</pre>\n",
        "</details>"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "05283db2-2f26-4800-8eda-78a4468a3d8f",
      "metadata": {
        "id": "05283db2-2f26-4800-8eda-78a4468a3d8f"
      },
      "source": [
        "## Part 7: Time Travel\n",
        "\n",
        "In a typical chat bot workflow, the user interacts with the bot 1 or more times to accomplish a task. In the previous sections, we saw how to add memory and a human-in-the-loop to be able to checkpoint our graph state and manually override the state to control future responses.\n",
        "\n",
        "But what if you want to let your user start from a previous response and \"branch off\" to explore a separate outcome? Or what if you want users to be able to \"rewind\" your assistant's work to fix some mistakes or try a different strategy (common in applications like autonomous software engineers)?\n",
        "\n",
        "You can create both of these experiences and more using LangGraph's built-in \"time travel\" functionality.\n",
        "\n",
        "In this section, you will \"rewind\" your graph by fetching a checkpoint using the graph's `get_state_history` method. You can then resume execution at this previous point in time.\n",
        "\n",
        "First, recall our chatbot graph. We don't need to make **any** changes from before:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "bb8a02de-a21b-4ef6-a714-7d6e44435e3a",
      "metadata": {
        "id": "bb8a02de-a21b-4ef6-a714-7d6e44435e3a"
      },
      "outputs": [],
      "source": [
        "from typing import Annotated, Literal\n",
        "\n",
        "from langchain_community.tools.tavily_search import TavilySearchResults\n",
        "from langchain_core.messages import AIMessage, ToolMessage\n",
        "\n",
        "# NOTE: you must use langchain-core >= 0.3 with Pydantic v2\n",
        "from pydantic import BaseModel\n",
        "from typing_extensions import TypedDict\n",
        "\n",
        "from langgraph.checkpoint.memory import MemorySaver\n",
        "from langgraph.graph import StateGraph, START\n",
        "from langgraph.graph.message import add_messages\n",
        "from langgraph.prebuilt import ToolNode, tools_condition\n",
        "\n",
        "\n",
        "class State(TypedDict):\n",
        "    messages: Annotated[list, add_messages]\n",
        "    # This flag is new\n",
        "    ask_human: bool\n",
        "\n",
        "\n",
        "class RequestAssistance(BaseModel):\n",
        "    \"\"\"Escalate the conversation to an expert. Use this if you are unable to assist directly or if the user requires support beyond your permissions.\n",
        "\n",
        "    To use this function, relay the user's 'request' so the expert can provide the right guidance.\n",
        "    \"\"\"\n",
        "\n",
        "    request: str\n",
        "\n",
        "\n",
        "tool = TavilySearchResults(max_results=2)\n",
        "tools = [tool]\n",
        "# We can bind the llm to a tool definition, a pydantic model, or a json schema\n",
        "llm_with_tools = llm.bind_tools(tools + [RequestAssistance])\n",
        "\n",
        "\n",
        "def chatbot(state: State):\n",
        "    response = llm_with_tools.invoke(state[\"messages\"])\n",
        "    ask_human = False\n",
        "    if (\n",
        "        response.tool_calls\n",
        "        and response.tool_calls[0][\"name\"] == RequestAssistance.__name__\n",
        "    ):\n",
        "        ask_human = True\n",
        "    return {\"messages\": [response], \"ask_human\": ask_human}\n",
        "\n",
        "\n",
        "graph_builder = StateGraph(State)\n",
        "\n",
        "graph_builder.add_node(\"chatbot\", chatbot)\n",
        "graph_builder.add_node(\"tools\", ToolNode(tools=[tool]))\n",
        "\n",
        "\n",
        "def create_response(response: str, ai_message: AIMessage):\n",
        "    return ToolMessage(\n",
        "        content=response,\n",
        "        tool_call_id=ai_message.tool_calls[0][\"id\"],\n",
        "    )\n",
        "\n",
        "\n",
        "def human_node(state: State):\n",
        "    new_messages = []\n",
        "    if not isinstance(state[\"messages\"][-1], ToolMessage):\n",
        "        # Typically, the user will have updated the state during the interrupt.\n",
        "        # If they choose not to, we will include a placeholder ToolMessage to\n",
        "        # let the LLM continue.\n",
        "        new_messages.append(\n",
        "            create_response(\"No response from human.\", state[\"messages\"][-1])\n",
        "        )\n",
        "    return {\n",
        "        # Append the new messages\n",
        "        \"messages\": new_messages,\n",
        "        # Unset the flag\n",
        "        \"ask_human\": False,\n",
        "    }\n",
        "\n",
        "\n",
        "graph_builder.add_node(\"human\", human_node)\n",
        "\n",
        "\n",
        "def select_next_node(state: State):\n",
        "    if state[\"ask_human\"]:\n",
        "        return \"human\"\n",
        "    # Otherwise, we can route as before\n",
        "    return tools_condition(state)\n",
        "\n",
        "\n",
        "graph_builder.add_conditional_edges(\n",
        "    \"chatbot\",\n",
        "    select_next_node,\n",
        "    {\"human\": \"human\", \"tools\": \"tools\", END: END},\n",
        ")\n",
        "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
        "graph_builder.add_edge(\"human\", \"chatbot\")\n",
        "graph_builder.add_edge(START, \"chatbot\")\n",
        "memory = MemorySaver()\n",
        "graph = graph_builder.compile(\n",
        "    checkpointer=memory,\n",
        "    interrupt_before=[\"human\"],\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "a7debb4a-2a3a-40b9-a48c-7052ec2c2726",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 308
        },
        "id": "a7debb4a-2a3a-40b9-a48c-7052ec2c2726",
        "outputId": "7ec5ba48-1c64-4ec1-d9e3-53ffff5f20ed"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAEjAaMDASIAAhEBAxEB/8QAHQABAAMBAAMBAQAAAAAAAAAAAAUGBwQCAwgBCf/EAFgQAAEEAQIDAggICQgGBgsAAAEAAgMEBQYRBxIhEzEIFBYiQVFWlBUyNlR0k9HUFzVVYXWytNLTIzdCUnGBkbMzQ2KSlaElV3OCorEJGCQnRFNyg4XBxP/EABoBAQEAAwEBAAAAAAAAAAAAAAABAgMEBQb/xAA3EQEAAQIBCQYEBAcBAAAAAAAAAQIRAxIhMUFRUmGh0QQUM3GRwQUjgZITQ2KxFSIyQsLh8PH/2gAMAwEAAhEDEQA/AP6poiICIiAiIgIiICIiAiIgIiICIiAvxzgxpc4hrQNySegCi85mX47sK1WA28laLm14N9m9B50jz/RjbuN3fnAALnAGOZoSpkHtsZ+Q6gt7h3LabtWjI/8AlwblrQD3E8zu7dx2W6miLZVc2jmttqUfqbDxOLX5Wixw9DrLAf8AzXj5VYX8sUPeWfavFmksHGwMZhse1o6BoqsAH/JeXkrhfyPQ92Z9iy+Tx5GY8qsL+WKHvLPtTyqwv5Yoe8s+1PJXC/keh7sz7E8lcL+R6HuzPsT5PHkuY8qsL+WKHvLPtTyqwv5Yoe8s+1PJXC/keh7sz7E8lcL+R6HuzPsT5PHkZjyqwv5Yoe8s+1e2tnsZckDK+RqTvJ2DY52uJ/uBXq8lcL+R6HuzPsXqsaM0/bjMc+Cxs0ZBHLJUjcOvQ94T5PHkZkyiq401Z0z/AC+n5ZXV29ZMRPKXxPb6RC5x3id6hvyHuIG/MJ3FZODM0Irlcu7OQHzZGlr2OB2c1zT1a5pBBB6gghYVUREZVM3j/tKWdaIi1IIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiCr6V2yucz+Xfs5wsnHVz/Uih6OH9plMpJHeA3f4oVoVY0OPFTn8e7cS1srYeQRtu2YidpHrG0u2/rBHoVnXRj+JMas1vK2bks6RcmXy1LAYq7k8jZipY+lC+xYszO5WRRsaXOe4+gAAkn8y61Aa/pUMloXUNTKYqxncbPj547OLqM5prcZjcHRRjcbucN2jqOpHUd650ZdrjwtNI4LhfktYYA29QMqWqlTxc4+5XJNiQBrzzQ78nJzuDtuVxaGh27272/N8etF6c03ic5k79+nRypkbUZJhrvjMnZnZ+9cQ9q0D1uYBsQe4gr53s4jXmqOCvEjTeOxWqstpjG/BNjTcWqKHiuXlENhk1msGuDXTNY2FoY9w3cXcu79t1fuJuts5rDI6MuQ4viDitA2Y7nwlBg8ZZq5Z1tpjFdkzWATxQkGU8zeUEhvMQNkGk5Pj/AMP8RhdN5exqOF2O1GH/AATPXglnFssbzOYwRscefpsGEBxd5oBd0Vcp+E3gb3GOnoiOjlRDcxFXI177sRea50k8nKyN7DAOyYG8rjK8hoLi0lpY4LIODGhM/jrfBKpkNL5ui3Aak1O+23JVnvNRk0dl8D5JfOa4O7VgEgcWufuASQtU1TYyGi/Cfp6km0/mspg8vpeLCsuYii+22vZZdfIRMGAmNpZKDzu83zT16INwREQFV8ftiNeZGizZtfJVW5BjB6JmOEcx/sIdB0HpDj3lWhVgjx3iUxzNy3HYlzJDt03sTNLRv69qxJHo3HrC6ML+6J0W/wDOdlhZ0RFzoIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiCv5mhYx2VbncfD4xL2QguVWnzp4WkuaWejtGFztgehDnDpuCPDJ4jS/FPT3iuToY/UmIdIHOrXYGzRtkb6HMePNe3fqCAQe8BWNQmV0bistdN18MlXIEAG7SmfXmcB3BzmEF4HqduOp6LfFVNcRFerX1XTpVNvg3cKWBwbw40u0PGzgMTB1G4Ox831gf4Lv0/wM4d6TzFbLYXQ+n8Tk6xJhuU8bFFLGSC08rmtBG4JH9hKkDoiYABmp88xo9HbxO/5mMlPImx7VZ766H+Er+Hh7/KS0bVoRVfyJse1We+uh/hKp8VcfldG6ByuYx2qcwblYRmMTywlnnSsadx2Y9Dj6U/Dw9/lJaNrVF+EBwII3B7wqx5E2ParPfXQ/wk8ibHtVnvrof4Sfh4e/yktG1Xv/Vr4T/9W2lf+EQfur9Pg2cJ3Ek8N9LEnvJxMBJ/8KsHkTY9qs99dD/CX6NDueOWfUWdsM67t8bEW4P542tP+BTIw9/lJaNrvy2eq4PsKMDG2MjK3arjoSA9wHTcj+jGPS89B/aQD5adwz8TWnksvZNkbkvjFuZgPK6QgDZu/Xla1rWj8zRv1JXswunMbp6OVmPqMrmUh0su5dJKR0Be9xLnnb0uJKkljVVTEZNGj9zyERFpQREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFnvH8gcIs/uSBtB3f9vH+cLQlnvH/f8EWf2232g+Ntt/p4/Wg0JERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFnnhAjfhDqDqG9IOpHT/TxrQ1nnhA7fgh1Bv0G0Ho3/18aDQ0REBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQERE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D4czPsZm/raX3lPhzM+xmb+tpfeVf0TvUbkc+pfgqGExGQymbrZXJU3YuKm17a1SSRr5XPeNi95Y4tADdwACT5xJI7lb0Rc2JiTiTeSZuIiLUgiIgIiICIiAiIgIiICIiAiIgIiII7UcL7GnspFG0ukfVla1o9JLCAq1pd7ZNNYlzTu11SEg+scgV2VTtcPm9vI/GZvJYOF7i81aYgfCHHqS1ssT+Xc9dmkDck7dV24OJTFM0VTbWuqzpRcHkBkPbPN/UUvu6eQGQ9s839RS+7rffD3459C3F3ouDyAyHtnm/qKX3dPIDIe2eb+opfd0vh78c+hbi70XB5AZD2zzf1FL7unkBkPbPN/UUvu6Xw9+OfQtxd6Lg8gMh7Z5v6il93TyAyHtnm/qKX3dL4e/HPoW4u9FweQGQ9s839RS+7p5AZD2zzf1FL7ul8Pfjn0LcXei4PIDIe2eb+opfd08gMh7Z5v6il93S+Hvxz6FuLvRcHkBkPbPN/UUvu6eQGQ9s839RS+7pfD3459C3F3ouDyAyHtnm/qKX3dPIDIe2eb+opfd0vh78c+hbi70XB5AZD2zzf1FL7uvJmgbm5Eurs1Mw97ezqM36+tsAI/uPpUvh78c+hbi/eH7CL2rZQd2S5cFp2PoqVmH/wATXD+5W9cmKxVXCY+GlShEFaIENbuXEkkkuJO5c4kklxJJJJJJJXWuLGrjErmqNHTMTnERFpQREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERB//9k=\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        }
      ],
      "source": [
        "from IPython.display import Image, display\n",
        "\n",
        "try:\n",
        "    display(Image(graph.get_graph().draw_mermaid_png()))\n",
        "except Exception:\n",
        "    # This requires some extra dependencies and is optional\n",
        "    pass"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5414c482-215e-4cc0-9eef-4a8722d2f468",
      "metadata": {
        "id": "5414c482-215e-4cc0-9eef-4a8722d2f468"
      },
      "source": [
        "Let's have our graph take a couple steps. Every step will be checkpointed in its state history:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "69071b02-c011-4b7f-90b1-8e89e032322d",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "69071b02-c011-4b7f-90b1-8e89e032322d",
        "outputId": "358cdea9-a67c-413c-bd2e-e1c81e446a54"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "I'm learning LangGraph. Could you do some research on it for me?\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "Tool Calls:\n",
            "  tavily_search_results_json (7e98a255-e576-4fff-ab15-fa5bc398a4f1)\n",
            " Call ID: 7e98a255-e576-4fff-ab15-fa5bc398a4f1\n",
            "  Args:\n",
            "    query: LangGraph\n",
            "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
            "Name: tavily_search_results_json\n",
            "\n",
            "[{\"url\": \"https://langchain-ai.github.io/langgraph/\", \"content\": \"LangGraph is a framework for creating stateful, multi-actor applications with LLMs, using cycles, controllability, and persistence. Learn how to use LangGraph with LangChain, LangSmith, and Anthropic tools to build agent and multi-agent workflows.\"}, {\"url\": \"https://langchain-ai.github.io/langgraph/tutorials/\", \"content\": \"LangGraph is a framework for building language agents as graphs. Learn how to use LangGraph to create chatbots, code assistants, planning agents, reflection agents, and more with these notebooks.\"}]\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "LangGraph is a framework for creating stateful, multi-actor applications with LLMs, using cycles, controllability, and persistence. You can learn more about it here: https://langchain-ai.github.io/langgraph/  It seems like you can also use LangGraph to create chatbots, code assistants, planning agents, reflection agents, and more. You can find some tutorials on how to do this here: https://langchain-ai.github.io/langgraph/tutorials/\n"
          ]
        }
      ],
      "source": [
        "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
        "events = graph.stream(\n",
        "    {\n",
        "        \"messages\": [\n",
        "            (\"user\", \"I'm learning LangGraph. Could you do some research on it for me?\")\n",
        "        ]\n",
        "    },\n",
        "    config,\n",
        "    stream_mode=\"values\",\n",
        ")\n",
        "for event in events:\n",
        "    if \"messages\" in event:\n",
        "        event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "acbec099-e5d2-497f-929e-c548d7bcbf77",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "acbec099-e5d2-497f-929e-c548d7bcbf77",
        "outputId": "ed74695c-e152-4c4a-d2ef-3316f6838466"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "Ya that's helpful. Maybe I'll build an autonomous agent with it!\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "That sounds like a fun project!  LangGraph is a powerful tool for building autonomous agents, and there are many interesting possibilities to explore.  What kind of agent are you thinking of building?  What tasks would you want it to be able to perform?\n"
          ]
        }
      ],
      "source": [
        "events = graph.stream(\n",
        "    {\n",
        "        \"messages\": [\n",
        "            (\"user\", \"Ya that's helpful. Maybe I'll build an autonomous agent with it!\")\n",
        "        ]\n",
        "    },\n",
        "    config,\n",
        "    stream_mode=\"values\",\n",
        ")\n",
        "for event in events:\n",
        "    if \"messages\" in event:\n",
        "        event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b2e48c77-65f3-4075-8030-ebf943a281f1",
      "metadata": {
        "id": "b2e48c77-65f3-4075-8030-ebf943a281f1"
      },
      "source": [
        "Now that we've had the agent take a couple steps, we can `replay` the full state history to see everything that occurred."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6c0dbed5-210d-40ad-b002-0bc52ef28fac",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "6c0dbed5-210d-40ad-b002-0bc52ef28fac",
        "outputId": "3ae39d7f-331b-45fc-9ef4-1c20c98acadb"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Num Messages:  6 Next:  ()\n",
            "--------------------------------------------------------------------------------\n",
            "Num Messages:  5 Next:  ('chatbot',)\n",
            "--------------------------------------------------------------------------------\n",
            "Num Messages:  4 Next:  ('__start__',)\n",
            "--------------------------------------------------------------------------------\n",
            "Num Messages:  4 Next:  ()\n",
            "--------------------------------------------------------------------------------\n",
            "Num Messages:  3 Next:  ('chatbot',)\n",
            "--------------------------------------------------------------------------------\n",
            "Num Messages:  2 Next:  ('tools',)\n",
            "--------------------------------------------------------------------------------\n",
            "Num Messages:  1 Next:  ('chatbot',)\n",
            "--------------------------------------------------------------------------------\n",
            "Num Messages:  0 Next:  ('__start__',)\n",
            "--------------------------------------------------------------------------------\n"
          ]
        }
      ],
      "source": [
        "to_replay = None\n",
        "for state in graph.get_state_history(config):\n",
        "    print(\"Num Messages: \", len(state.values[\"messages\"]), \"Next: \", state.next)\n",
        "    print(\"-\" * 80)\n",
        "    if len(state.values[\"messages\"]) == 6:\n",
        "        # We are somewhat arbitrarily selecting a specific state based on the number of chat messages in the state.\n",
        "        to_replay = state"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b182019e-bae3-4616-ba1b-f845c0ab6636",
      "metadata": {
        "id": "b182019e-bae3-4616-ba1b-f845c0ab6636"
      },
      "source": [
        "**Notice** that checkpoints are saved for every step of the graph. This __spans invocations__ so you can rewind across a full thread's history. We've picked out `to_replay` as a state to resume from. This is the state after the `chatbot` node in the second graph invocation above.\n",
        "\n",
        "Resuming from this point should call the **action** node next."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "de8d5521-8d71-4093-a657-4920c790802f",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "de8d5521-8d71-4093-a657-4920c790802f",
        "outputId": "ba147679-244c-409d-ed77-db72892da83c"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "()\n",
            "{'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef973ed-cb57-677a-8006-950832180416'}}\n"
          ]
        }
      ],
      "source": [
        "print(to_replay.next)\n",
        "print(to_replay.config)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7e8c61f5-3a4a-4cce-b81b-43fe1dcc971f",
      "metadata": {
        "id": "7e8c61f5-3a4a-4cce-b81b-43fe1dcc971f"
      },
      "source": [
        "**Notice** that the checkpoint's config (`to_replay.config`) contains a `checkpoint_id` **timestamp**. Providing this `checkpoint_id` value tells LangGraph's checkpointer to **load** the state from that moment in time. Let's try it below:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "85f17be3-eaf6-495e-a846-49436916b4ab",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "85f17be3-eaf6-495e-a846-49436916b4ab",
        "outputId": "5ea89092-3776-437e-d976-5f4e5082e7d8"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "That sounds like a fun project!  LangGraph is a powerful tool for building autonomous agents, and there are many interesting possibilities to explore.  What kind of agent are you thinking of building?  What tasks would you want it to be able to perform?\n"
          ]
        }
      ],
      "source": [
        "# The `checkpoint_id` in the `to_replay.config` corresponds to a state we've persisted to our checkpointer.\n",
        "for event in graph.stream(None, to_replay.config, stream_mode=\"values\"):\n",
        "    if \"messages\" in event:\n",
        "        event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c2501fed-2591-420d-98e0-4a3836fb99a8",
      "metadata": {
        "id": "c2501fed-2591-420d-98e0-4a3836fb99a8"
      },
      "source": [
        "Notice that the graph resumed execution from the `**action**` node. You can tell this is the case since the first value printed above is the response from our search engine tool.\n",
        "\n",
        "**Congratulations!** You've now used time-travel checkpoint traversal in LangGraph. Being able to rewind and explore alternative paths opens up a world of possibilities for debugging, experimentation, and interactive applications."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e584d57f-5aad-4507-815f-0b2e4b64b791",
      "metadata": {
        "id": "e584d57f-5aad-4507-815f-0b2e4b64b791"
      },
      "source": [
        "## Conclusion\n",
        "\n",
        "Congrats! You've completed the intro tutorial and built a chat bot in LangGraph that supports tool calling, persistent memory, human-in-the-loop interactivity, and even time-travel!\n",
        "\n",
        "The [LangGraph documentation](https://langchain-ai.github.io/langgraph/) is a great resource for diving deeper into the library's capabilities."
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3 (ipykernel)",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.11.9"
    },
    "colab": {
      "provenance": []
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}